Tutoriel de David Shaffer/Interlude: Nettoyer PersonalInformationView

From OFSET Wiki

Jump to: navigation, search

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). Cette approche n'est pas très orientée 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 ceci :

...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 :

Image:ListView.png

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.
Personal tools