Tutoriel de David Shaffer/Interlude: Nettoyer PersonalInformationView
From OFSET Wiki
Traduction de CleaningUp
[edit] Un modèle de données
PersonnalInformationView a besoin de travail. Si nous construisons un carnet d'adresses ou quelque chose dans ce genre, quand il faudra afficher les informations de quelqu'un, nous devrons remplir chacun des champs de la vue avec des données depuis notre source de données (probablement issues d'une base de données). Cela ne sonne pas très orienté objet. Nous devrions plutôt avoir un un modèle qui représente cette information et la vue permettrait tout simplement d'éditer un tel modèle. C'est pour cela que nous devrions avoir une classe PersonalInformation (ou Person si vous préférez) qui représente les informations d'une personne :
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 name
Cela permet de décrire notre base de données comme une simple liste de ces objets. Nous pourrions décider d'afficher la liste, d'éditer les objets, etc...
Noter le protocole pour les mises à jour d'email. Le modèle ne fournit pas directement les accesseurs mais plutôt les méthodes doSendEmailUpdates, doNotSetEmailUpdates, sendEmailUpdates. Notez aussi que les méthodes portant sur l'attribut gender suivent le même protocole. J'ai écrit le modèle de manière à illustrer un point : votre modèle est tel qu'il est. Souvent le modèle et l'interface (Web ou graphique) utilisateur nécessitent pas mal de travail pour les adapter l'un à l'autre. Par exemple, nous ne serons pas capable de faire quelque chose comme ça :
...inside render method html textInputOn: #gender of: model
parce que la méthode textInputOn:of: reposent sur les accesseurs de l'attribut. Dans ce cas, nous pouvons utiliser textInputWithValue:callback: ou un "intermédiaire" ou le patron Adapter dans notre classe représentant la vue. J'explique chacune de ces méthodes ci-dessous. Avant de nous tourner vers l'UI, créons une méthode de classe qui retourne un exemple de personne. Cette méthode nous permettra de pouvoir manipuler un objet "PersonalInformation" :
sample1
^PersonalInformation new
name: 'SpongeBob SquarePants';
address: 'a Pineapple, Bikini Bottom';
male;
doNotSendEmailUpdates;
birthdate: ('05/12/1999' asDate);
yourself.
La nouvelle vue.
Maintenant, nous avons besoin de modifier PersonalInformationView de manière à ce qu'elle puisse éditer une instance de 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" ...
J'ai essayé de de faire en sorte que la méthode renderContentOn: soit quelque peu lisible mais elle est encore un peu brouillon. Nous verrons plus tard qie plus d'abstraction permet d'éviter des méthodes comme celle-ci. Pour l'instant, parcourons la instruction par instruction. Comparez-la à la même méthode que la vue originale. Notez que là où l'ancien code indiquait "self" comme cible (textInputOn:of:), nous avons maintenant le modèle. Ainsi, la vue recupérera ses données et les stockera dans les objets du modèle. Comment utiliserons nous ceci ? Et bien, pour jouer avec, ajoutez une ancre avec un callback à votre composant HelloWorldComponent avec un callback identique à celui-ci :
editPersonalInformation | view | view := PersonalInformationView2 new. view model: PersonalInformation sample1. self call: view. self inform: 'Hello ' , view model name
Plus tar dans ce tutorial, nous verrons une vue similaire à celle-ci écrite avec beaucoup moins de lignes de code. C'est parce que Seaside inclue un outil permettant de construire des fenêtres d'édition à deux colonnes. Si vous voulez prendre de l'avance, explorez la classe WAEditDialog.
Qu'est-ce que nous avons obtenu ? Juste au cas où il ne serait pas évident de comprendre pourquoi nous avons fait cela, fabriquons une application de carnet d'adresses. Nous allons stocker les données dans l'image Smalltalk, pas dans une "vraie" base de données pour l'instant. Premièrement, créons une fausse base de données dans la classe PersonalInformation en ajoutant une variable de classe Database et quelque méthodes de classe :
Object subclass: #PersonalInformation instanceVariableNames: 'name address gender sendEmailUpdates birthdate' classVariableNames: 'Database' poolDictionaries: category: 'SCSeasideTutorial'
"Ce sont des méthodes de classe !!!!!!!!!"
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.
Maintenant faisons une vue pour lister ces objets et qui nous permette de les éditer.
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.
Si tout va bien, vous pouvez visualiser cette application et vous devriez voir :
Un clic sur un lien ouvre l'éditeur. Un appuie sur le bouton save met à jour notre "database". Faites une pause pour le moment pour penser comment l'éditeur fait son travail : vous lui donnez un modèle, il affiche le contenu du modèle, l'utilisateur édite les champs et presse "save", le formulaire est envoyé et le modèle est rempli automatiquement par tout les champs callbacks, l'éditeur répond vrai et ainsi la liste est affichée avec le modèle édité.
[edit] Exercices
- Ajouter des liens pour ajouter et supprimer des entrées de la liste.
- Transformez les entêtes du tableau en liens de manière à ce que lorsque nous cliquons dessus, la liste soit trier par nom, adresse, etc...
- Ajouter une méthode style améliorer le look de la liste (aligner à droite la date, ajouter des bordures, etc...).
- Essayer de penser à comment vous feriez pour ajouter un bouton "cancel". C'est plus subtile que vous ne le pensez. Un simple self answer false: n'est pas suffisant puisque le modèle a déjà été rempli avec de nouvelles valeurs. Le même problème apparaît dans les application GUI quand la vue édite directement le modèle. Il y a plusieurs solutions simples...essayez d'en trouver une.


