PersonalInformationViewPersonalInformationView needs some work. If we're going
to build an addressbook or some such thing, when its time to display
someone's "information", we'd have to fill in each of the
view's fields using data from our data source (presumably a database).
Doesn't sound very object-oriented. Instead we should have a
business (model) object which represents this information and the
view should simply edit such an object. That is we should have a
class to represent PersonalInformation (or even
Person if you prefer):
Object subclass: #PersonalInformation instanceVariableNames: 'name address gender sendEmailUpdates birthdate' classVariableNames: '' poolDictionaries: '' category: 'SCSeasideTutorial' address ^address address: anObject address := anObject birthdate ^birthdate birthdate: anObject birthdate := anObject doNotSendEmailUpdates sendEmailUpdates := false doSendEmailUpdates sendEmailUpdates := true female gender := #Female isFemale ^self isMale not isMale ^gender = #Male male gender := #Male name ^name name: anObject name := anObject sendEmailUpdates ^sendEmailUpdates printOn: aStream aStream nextPutAll: self namePicture our database as simply a list of these objects. We might want to show the list, edit the objects etc.
Notice the protocol for email updates. The model doesn't provide
direct mutators but instead the methods #doSendEmailUpdates,
#doNotSetEmailUpdates, #sendEmailUpdates. Also notice that the
gender property follows a similar pattern. I wrote the model this way
to illustrate a point: your model is what it is. Often the model and
the (web or graphical) UI require some work to adapt to one another.
For example, we won't be able to just do something like:
...inside render method html textInputOn: #gender of: modelbecause the textInputOn:of: relies on accessors and mutators for the property. In this case we can either use
textInputWithValue:callback: or use a "go-between" or
adapter method in our view class. I show both methods below. Before
we move on to the UI lets make a class side method which gives
us a sample person to play with:
sample1
^PersonalInformation new
name: 'SpongeBob SquarePants';
address: 'a Pineapple, Bikini Bottom';
male;
doNotSendEmailUpdates;
birthdate: ('05/12/1999' asDate);
yourself.
Now, we need to modify PersonalInformationView so that
it can edit an instance of PersonalInformation. Let's
just start fresh.
WAComponent subclass: #PersonalInformationView2 instanceVariableNames: 'model' classVariableNames: '' poolDictionaries: '' category: 'SCSeasideTutorial' model ^model model: anObject model := anObject renderContentOn: html html form: [html spanClass: 'label' with: 'Name:'. html spanClass: 'field' with: [html textInputOn: #name of: model]. html spanClass: 'label' with: 'Address:'. html spanClass: 'field' with: [html textAreaOn: #address of: model]. html spanClass: 'label' with: 'Gender:'. html spanClass: 'field' with: [|group| group := html radioGroup. html radioButtonInGroup: group selected: model isMale callback: [model male]. html text: 'Male'; break. html radioButtonInGroup: group selected: model isFemale callback: [model female]. html text: 'Female']. html spanClass: 'label' with: 'Updates:'. html spanClass: 'field' with: [html checkboxOn: #sendEmailUpdates of: self]. html spanClass: 'label' with: 'Birthdate:'. html spanClass: 'field' with: [html dateInputWithValue: model birthdate callback: [:v | model birthdate: v]]. html spanClass: 'button' with: [html submitButtonWithAction: [self save] text: 'Save']] sendEmailUpdates ^model sendEmailUpdates sendEmailUpdates: aBoolean aBoolean ifTrue: [model doSendEmailUpdates] ifFalse: [model doNotSendEmailUpdates] save self answer: true style "Same as PersonalInformationView" ...I've tried to make the
renderContentOn: method somewhat
readable but its still a mess. We'll see later that further
abstraction helps to avoid methods like this one. For now, walk
through it one statement at a time. Compare it to the same method in
the original view. Notice in places where the old code specified
"self" as the target (#textInputOn:of:) we now specify the
model. So, the view will retrieve its data and store its data right in
our model objects. How would we use this? Well, to play with it add
an anchor with a callback to your HelloWorldComponent and make
the callback look like this:
editPersonalInformation | view | view := PersonalInformationView2 new. view model: PersonalInformation sample1. self call: view. self inform: 'Hello ' , view model name
Later in this tutorial we will see a view similar to this one
written in far fewer lines of code. This is because Seaside includes
support for building simple two-column editor dialogs. If you want to
get ahead, browse the WAEditDialog class.
PersonalInformation
class by adding a class variable Database and some
class side methods:
Object subclass: #PersonalInformation
instanceVariableNames: 'name address gender sendEmailUpdates birthdate'
classVariableNames: 'Database'
poolDictionaries: ''
category: 'SCSeasideTutorial'
"These are class side methods!!!!!!!!!"
database
^Database ifNil: [self resetSampleDatabase]
resetSampleDatabase
"self resetSampleDatabase"
Database := OrderedCollection
with: self sample1
with: self sample2
with: self sample3.
^Database
sample1
^PersonalInformation new
name: 'SpongeBob SquarePants';
address: 'a Pineapple, Bikini Bottom';
male;
doNotSendEmailUpdates;
birthdate: ('05/12/1999' asDate);
yourself.
sample2
^PersonalInformation new
name: 'Patrick Star';
address: 'Under a rock';
male;
doSendEmailUpdates;
birthdate: ('01/12/1998' asDate);
yourself.
sample3
^PersonalInformation new
name: 'Sandy Cheeks';
address: 'A biodome';
femal;
doSendEmailUpdates;
birthdate: ('01/01/1980' asDate);
yourself.
Now lets make a view which lists these and allows us to edit them:
WAComponent subclass: #AddressBook instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'SCSeasideTutorial' editPerson: person | view | view := PersonalInformationView2 new. view model: person. self call: view 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] 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.If all goes well you can visit this application and you should see:

true and
so the list is displayed with the edited model object.
#style method and make the list look better
(right justify the date, maybe add borders etc).self answer:
false isn't enough since the model has already been filled in
with the new values. This same problem occurs in GUI
applications where a view directly edits a model. There are several
simple solutions...try to find one.