Embedding components

David Shaffer
Shaffer Consulting

Introduction

Building reusable components and frameworks is the goal of any developer in almost all parts of their applications. The dearth of truely reusable (canned) component libraries for most of the existing web development frameworks is a good indication that this is difficult to do. I think Seaside is among the few frameworks perched to change this. It has a solid component model giving one all of the mechanisms necessary to develop well encapsulated components and application development frameworks. We have seen, in a previous section, that components can be sequenced. In this section we show how to embed one component inside another component. In the next section, Decorations, we will see how to decorate a component to add functionality or change its appearence. These three tools make writing Seaside applications very similar to writing GUI applications (easier in some ways!).

Basics

What we'd like to do is adapt our address book application so that we can embed the editor on the same page as the address list itself:

We already have a working editor component so let's just add it to our AddressBook component. That is, we're going to embed the PersonalInformationView2 component inside the AddressBook component. Here are the steps:

  1. Add an instance variable (editor) to AddressBook.
  2. Add an #initialize method to AddressBook which creates the editor and gives it a model to edit.
  3. Add a #children method to AddressBook which returns an array containing the editor. This is needed because Seaside needs to be able to figure out, without rendering, what components are embedded within your component.
  4. Modify #editPerson: so that it just installs the model in the editor.
  5. Modify #renderContentOn: so that it renders the editor
Here is the source code for AddressBook after these changes:
WAComponent subclass: #AddressBook
	instanceVariableNames: 'editor'
	classVariableNames: ''
	poolDictionaries: ''
	category: 'SCSeasideTutorial'

initialize
	editor := PersonalInformationView2 new.
	editor model: PersonalInformation database first 

children
	^Array with: editor 

editPerson: person
	editor model: person 

people
	^PersonalInformation database 

renderContentOn: html
	html heading: 'My friends' level: 1.
	html table: 
		[html tableRow:
			[html tableHeading: 'Name'. html tableHeading: 'Address'. html tableHeading: 'Birthdate'].
		self renderDatabaseRowsOn: html].
	html hr.
	html render: editor 

renderDatabaseRowsOn: html
	self people do: [:person |
		html tableRow: [self renderPerson: person on: html]] 

renderPerson: person on: html
	html tableData: [html anchorWithAction: [self editPerson: person] text: person name].
	html tableData: person address.
	html tableData: person birthdate mmddyyyy. 
Try the application...make sure that the editor is doing its job. Activate the halos. You'll notice halos around each of the components. Experiment with the halo buttons to remind yourself what they do. It is very helpful to inspect the state of a component in a running application (or view the rendered HTML).

Intercepting a subcomponent's answer

Many components are designed to support both standalone and embedded use. Such components often produce answers (call answer) in response to user actions. When the component is used standalone this answer is given back to the caller. If the component is embedded this answer is ignored unless the parent component arranges to intercept it. In our example above the editor "answers" when the users presses save but this answer is ignored. This type of thing happens quite a bit since components are often usable both embedded or called. When a component wants to respond to one of its subcomponents answers, it arranges to do so via the #onAnswer: method. In the interest of an example, let's say we want to give the user confirmation that their data was saved. Change AddressBook>>initialize to:
initialize
	editor := PersonalInformationView2 new.
	editor model: PersonalInformation database first.
	editor onAnswer: [:ans | self inform: 'Saved']
Now restart your application (press "New Session") and try it out. Note that the components answer is passed into the block (although we didn't use it in this example).

Why do I keep getting "Error: Components not found while processing callbacks: #(...)"?

Something is wrong with your children method. Either you are just not correctly instantiating your subcomponent or you are trying to do something fancy with subcomponents and it isn't working. Some things to check:
C. David Shaffer
Last modified: Fri Jul 22 17:28:44 EDT 2005