Tutoriel de David Shaffer/Composants enchassés
From OFSET Wiki
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:
* Look at your rendering method(s), are they sending html render: component for some components? If so, are those components returned in your children method? * Are you changing one of your subcomponents during rendering? That is, your subcomponents are stored in instance variables, are you changing the value of one of these instance variables during rendering? If so, look for a way to avoid this.

