% -*- mode: latex; -*- mustache tags:  
\ifx\wholebook\relax\else

	\documentclass[10pt,twoside,english]{root/support/latex/sbabook/sbabook}
  \usepackage{import}
  \subimport{root/support/latex/}{common.tex}

  \hypersetup{pdfinfo = {
    Title = {Interface Web d'administration pour TinyBlog},
    Author = {},
    Keywords = {pharo, smalltalk}}}
  \begin{document}
\fi

\chapter{Correction semaine pr\'{e}c\'{e}dente}
Vous pouvez charger la correction de la semaine pr\'{e}c\'{e}dente en ex\'{e}cutant le code suivant:

\begin{displaycode}{smalltalk}
Gofer new 
   smalltalkhubUser: 'PharoMooc' project: 'TinyBlog';
   package: 'ConfigurationOfTinyBlog';
   load. 
#ConfigurationOfTinyBlog asClass loadWeek4Correction
\end{displaycode}

Pour tester le code, vous devez lancer le serveur HTTP pour Seaside  avec l'outil Seaside Control Panel (cf. sujet semaine pr\'{e}c\'{e}dente) ou avec le code suivant :

\begin{displaycode}{smalltalk}
ZnZincServerAdaptor startOn: 8080.
\end{displaycode}

Vous devrez \'{e}galement faire :

\begin{displaycode}{smalltalk}
TBBlog reset ; 
	createDemoPosts
\end{displaycode}
\chapter{D\'{e}crire les donn\'{e}es m\'{e}tiers avec Magritte}
Magritte est une biblioth\`{e}que qui permet une fois les donn\'{e}es d\'{e}crites de g\'{e}n\'{e}rer diverses repr\'{e}sentations ou op\'{e}rations (telles des requ\^{e}tes). Coupl\'{e} avec Seaside, Magritte permet de g\'{e}n\'{e}rer des formulaires et des rapports.  Le logiciel Quuve de la soci\'{e}t\'{e} Debris Publishing est un brillant exemple de la puissance de Magritte: tous les tableaux sont automatiquement g\'{e}n\'{e}r\'{e}s (voir \url{http://www.pharo.org/success}). La validation des donn\'{e}es est aussi d\'{e}finie au niveau de Magritte au lieu d'\^{e}tre dispers\'{e}e dans le code de l'interface graphique. Ce chapitre ne montre pas cet aspect.

Un chapitre dans le livre sur Seaside (\url{http://book.seaside.st}) est disponible sur Magritte ainsi qu'un tutoriel sur \url{https://github.com/SquareBracketAssociates/Magritte}.

Dans ce chapitre, nous allons d\'{e}crire les cinq variables d'instance de l'objet \textcode{TBPost} \`{a} l'aide de Magritte. Dans le chapitre suivant, nous tirerons avantage de ces descriptions pour g\'{e}n\'{e}rer automatiquement des composants Seaside. 
\section{Descriptions}
Les cinq m\'{e}thodes suivantes sont dans le protocole 'descriptions' de la classe \textcode{TBPost}.
Noter que le nom des m\'{e}thodes n'est pas important mais que nous suivons une convention. C'est le pragma \textcode{\textless{}magritteDescription\textgreater{}} qui permet \`{a} Magritte d'identifier les descriptions. 

Le titre d'un post est une chaine de caract\`{e}res devant \^{e}tre obligatoirement compl\'{e}t\'{e}e.

\begin{displaycode}{smalltalk}
TBPost >> descriptionTitle
   <magritteDescription>
   ^ MAStringDescription new
      accessor: #title;
      beRequired;
      yourself
\end{displaycode}

Le texte d'un post est une chaine de caract\`{e}res multi-lignes devant \^{e}tre obligatoirement compl\'{e}t\'{e}e.

\begin{displaycode}{smalltalk}
TBPost >> descriptionText
   <magritteDescription>
   ^ MAMemoDescription new
      accessor: #text;
      beRequired;
      yourself
\end{displaycode}

La cat\'{e}gorie d'un post est une chaine de caract\`{e}res qui peut ne pas \^{e}tre renseign\'{e}e. Dans ce cas, le post sera de toute mani\`{e}re rang\'{e} dans la cat\'{e}gorie 'Unclassified'.

\begin{displaycode}{smalltalk}
TBPost >> descriptionCategory
   <magritteDescription>
   ^ MAStringDescription new
      accessor: #category;
      yourself
\end{displaycode}

La date de cr\'{e}ation d'un post est importante car elle permet de d\'{e}finir l'ordre de tri pour l'affichage des posts. C'est donc une variable d'instance contenant obligatoirement une date.

\begin{displaycode}{smalltalk}
TBPost >> descriptionDate
   <magritteDescription>
   ^ MADateDescription new
      accessor: #date;
      beRequired;
      yourself
\end{displaycode}

La variable d'instance \textcode{visible} doit obligatoirement contenir une valeur bool\'{e}enne.

\begin{displaycode}{smalltalk}
TBPost >> descriptionVisible
   <magritteDescription>
   ^ MABooleanDescription new
      accessor: #visible;
      beRequired;
      yourself
\end{displaycode}

   
\section{Am\'{e}liorations possibles}
Voici quelques am\'{e}liorations ou extensions possibles. 
Nous pourrions enrichir les descriptions pour qu'il ne soit pas possible de poster un post ayant une date ant\'{e}rieure \`{a} celle du jour. Nous pourrions changer la description d'une cat\'{e}gorie pour que ses valeurs possibles soient d\'{e}finies par l'ensemble des cat\'{e}gories existantes. Tout cela permettrait de produire des interfaces plus compl\`{e}tes et toujours aussi simplement.
\chapter{Administration de TinyBlog}
Nous allons aborder maintenant l'administration de TinyBlog. Cet exercice va nous permettre de montrer comment utiliser des informations de session ainsi que Magritte pour la d\'{e}finition de rapports. 

Le sc\'{e}nario assez classique que nous allons d\'{e}velopper est le suivant : l'utilisateur doit s'authentifier pour acc\'{e}der \`{a} la partie administration de TinyBlog. Il le fait \`{a} l'aide d'un compte et d'un mot de passe. Le lien permettant d'afficher le composant d'authentification sera plac\'{e} sous la liste des cat\'{e}gories.
\section{Composant d'identification}
Nous allons commencer par d\'{e}velopper un composant d'identification qui lorsqu'il sera invoqu\'{e} ouvrira une boite de dialogue pour demander les informations d'identification. Remarquer qu'une telle fonctionnalit\'{e} devrait faire partie d'une biblioth\`{e}que de composants de base en Seaside.

Ce composant va nous permettre d'illustrer comment la saisie de champs utilise de mani\`{e}re \'{e}l\'{e}gante les variables d'instances du composant. 

\begin{displaycode}{smalltalk}
WAComponent subclass: #TBAuthentificationComponent
   instanceVariableNames: 'password account component'
   classVariableNames: ''
   category: 'TinyBlog-Components'

TBAuthentificationComponent >> account
   ^ account

TBAuthentificationComponent >> account: anObject
   ^ account := anObject

TBAuthentificationComponent >> password
   ^ password

TBAuthentificationComponent >> password: anObject
   ^ password := anObject

TBAuthentificationComponent >> component
   ^ component

TBAuthentificationComponent >> component: anObject
   component := anObject
\end{displaycode}

La variable d'instance \textcode{component} est initialis\'{e}e par la m\'{e}thode de classe suivante :

\begin{displaycode}{smalltalk}
TBauthenticationComponent class >> from: aComponent
   ^ self new component: aComponent
\end{displaycode}

La m\'{e}thode \textcode{renderContentOn:} d\'{e}finit le contenu d'une boite de dialogue modale.

\begin{displaycode}{smalltalk}
TBAuthentificationComponent >> renderContentOn: html

   html tbsModal id: 'myAuthDialog'; with: [
    html tbsModalDialog: [
      html tbsModalContent: [
        html tbsModalHeader: [
          html tbsModalCloseIcon.
          html tbsModalTitle level: 4; with: 'Authentification'
        ].
        html tbsModalBody: [
          html form: [
            html text: 'Account:'.
            html break.
    html textInput
              callback: [ :value | account := value ];
              value: account.
    html break.
    html text: 'Password:'.
    html break.
    html passwordInput
      callback: [ :value | password := value ];
      value: password.
    html break.
    html break.
    html tbsModalFooter: [
      html tbsSubmitButton value: 'Cancel'.
        html tbsSubmitButton
          bePrimary;
            callback: [ self validate ];
          value: 'SignIn'.
      ] ] ] ] ] ]
\end{displaycode}
\subsubsection{Critique}
Quelle critique peut on faire sur cette m\'{e}thode ? Proposer une refactorisation \`{a} l'aide de plusieurs m\'{e}thodes pour d\'{e}finir les diff\'{e}rents \'{e}l\'{e}ments du formulaire de mani\`{e}re modulaire.
\subsubsection{Notre solution}
\begin{displaycode}{smalltalk}
TBAuthentificationComponent >> renderContentOn: html
   html tbsModal
      id: 'myAuthDialog';
      with: [ html tbsModalDialog: [ 
                     html tbsModalContent: [ 
                           self renderHeaderOn: html.
                           html
                              tbsModalBody: [ 
                                 html
                                    form: [ 
                                       self renderInputAccountOn: html. 
                                       html break.
                                       self renderInputPasswordOn: html. 
                                       html break.
                                       html break.
                                       html tbsModalFooter: [ self renderOkCancelOn: html ] ] ] ] ] ]
\end{displaycode}

\begin{displaycode}{smalltalk}
TBAuthentificationComponent >> renderHeaderOn: html
   html
      tbsModalHeader: [ 
         html tbsModalCloseIcon.
         html tbsModalTitle
            level: 4;
            with: 'Authentification' ]
\end{displaycode}

\begin{displaycode}{smalltalk}
TBAuthentificationComponent >> renderInputAccountOn: html
   html text: 'Account:'.
   html break.
   html textInput
         callback: [ :value | account := value ];
         value: account
\end{displaycode}

\begin{displaycode}{smalltalk}
TBAuthentificationComponent >> renderInputPasswordOn: html
   html text: 'Password:'.
   html break.
   html passwordInput
      callback: [ :value | password := value ];
      value: password
\end{displaycode}

\begin{displaycode}{smalltalk}
TBAuthentificationComponent >> renderOkCancelOn: html
   renderOkCancelOn: html
      html tbsButton 
		   attributeAt: 'type' put: 'button';
		   value: 'Cancel'.
      html tbsSubmitButton
         bePrimary;
         callback: [ self validate ];
         value: 'SignIn'
\end{displaycode}

Lorsque l'utilisateur clique sur le bouton 'SignIn', le message \textcode{validate} est envoy\'{e} et v\'{e}rifie que l'utilisateur a bien le compte 'admin' et a saisi le mot de passe 'password'.

\begin{displaycode}{smalltalk}
TBAuthentificationComponent >> validate
   (self account = 'admin' and: [ self password = 'password' ]) 
      ifTrue: [ self alert: 'Success!' ]
\end{displaycode}
\subsubsection{Critique }
Rechercher une autre m\'{e}thode pour r\'{e}aliser l'authentification de l'utilisateur (utilisation d'un backend de type base de donn\'{e}es, LDAP ou fichier texte). En tout cas, ce n'est pas \`{a} la boite de login de faire ce travail, il faut le d\'{e}l\'{e}guer \`{a} un objet m\'{e}tier qui saura consulter le backend et authentifier l'utilisateur.

De plus le composant \textcode{TBAuthentificationComponent} pourrait afficher l'utilisateur lorsque celui-ci est logu\'{e}.
\subsection{Int\'{e}gration de l'authentification}
Il faut maintenant int\'{e}grer le lien qui d\'{e}clenchera l'affichage de la boite modale d'authentification. Au tout d\'{e}but de la m\'{e}thode \textcode{renderContentOn:} du composant \textcode{TBPostsListComponent}, on ajoute le rendu du composant d'authentification. Ce composant re\c{c}oit en param\`{e}tre la r\'{e}f\'{e}rence vers le composant affichant les posts.

\begin{displaycode}{smalltalk}
TBPostsListComponent >> renderContentOn: html
   super renderContentOn: html.
   html render: (TBAuthentificationComponent from: self).
   html
      tbsContainer: [ 
         html tbsRow
            showGrid;
            with: [ self renderCategoryColumnOn: html.
                  self renderPostColumnOn: html ] ] 
\end{displaycode}

 On d\'{e}finit maintenant une m\'{e}thode qui affiche un pictogramme cl\'{e} et un lien 'SignIn'.

\begin{displaycode}{smalltalk}
TBPostsListComponent >> renderSignInOn: html
   html tbsGlyphIcon perform: #iconLock.
   html html: '<a data-toggle="modal" href="#myAuthDialog" class="link">SignIn</a>'.
\end{displaycode}

Nous ajoutons le composant d'authentification dessous la liste de categories.

\begin{displaycode}{smalltalk}
TBPostsListComponent >> renderCategoryColumnOn: html
   html tbsColumn
      extraSmallSize: 12;
      smallSize: 2;
      mediumSize: 4;
      with: [ 
		   self basicRenderCategoriesOn: html.
         self renderSignInOn: html ]
\end{displaycode}

Lorsque nous pressons sur le lien SignIn nous obtenons la figure \ref{authentification}.


\begin{figure}

\begin{center}
\includegraphics[width=0.75\textwidth]{figures/Authentification.png}\caption{Avec un meilleur agencement.\label{authentification}}\end{center}
\end{figure}

\section{Administration des posts}
Nous allons d\'{e}velopper deux composants. Le premier sera un rapport qui contiendra tous les posts et le second contiendra ce rapport. Le rapport \'{e}tant g\'{e}n\'{e}r\'{e} par Magritte sous la forme d'un composant Seaside, nous aurions pu n'avoir qu'un seul composant. Toutefois, nous pensons que distinguer le composant d'administration du rapport est une bonne chose pour l'\'{e}volution de la partie administration. Commen\c{c}ons donc par le composant d'administration. 
\subsection{Cr\'{e}ation d'un composant d'administration}
Le composant \textcode{TBAdminComponent} h\'{e}rite de \textcode{TBScreenComponent} pour b\'{e}n\'{e}ficier du header et de l'acc\`{e}s au blog. Il contiendra en plus le rapport que nous construisons par la suite. 

\begin{displaycode}{smalltalk}
TBScreenComponent subclass: #TBAdminComponent
   instanceVariableNames: ''
   classVariableNames: ''
   category: 'TinyBlog-Components'
\end{displaycode}

Nous d\'{e}finissons une premi\`{e}re version de la m\'{e}thode de rendu afin de pourvoir tester. 

\begin{displaycode}{smalltalk}
TBAdminComponent >> renderContentOn: html
   super renderContentOn: html.
   html tbsContainer: [
      html heading: 'Blog Admin'.
      html horizontalRule ]
\end{displaycode}

Nous modifions la m\'{e}thode \textcode{validate} pour qu'elle invoque la m\'{e}thode \textcode{gotoAdministration} d\'{e}finie dans le composant \textcode{TBPostsListComponent}. Cette derni\`{e}re m\'{e}thode invoque le composant d'administration. 

\begin{displaycode}{smalltalk}
TBPostsListComponent >> gotoAdministration
   self call: TBAdminComponent new
\end{displaycode}

\begin{displaycode}{smalltalk}
TBAuthentificationComponent >> validate
   (self account = 'admin' and: [ self password = 'password' ])
      ifTrue: [ self component gotoAdministration ]
\end{displaycode}

Pour tester, identifiez-vous sur l'application et vous devez obtenir la situation telle que repr\'{e}sent\'{e}e par la figure \ref{adminSimple}.


\begin{figure}

\begin{center}
\includegraphics[width=0.75\textwidth]{figures/BlogAdminSimple.png}\caption{Un composant d'administration vide.\label{adminSimple}}\end{center}
\end{figure}

\subsection{Le composant Rapport}
La liste des posts est affich\'{e}e \`{a} l'aide d'un rapport g\'{e}n\'{e}r\'{e} dynamiquement par le framework Magritte. Nous utilisons ce framework pour r\'{e}aliser les diff\'{e}rentes fonctionnalit\'{e}s de la partie administration de TinyBlog (liste des posts, cr\'{e}ation, \'{e}dition et suppression d'un post).

Pour rester modulaire, nous allons cr\'{e}er un composant Seaside pour cette t\^{a}che. 

\begin{displaycode}{smalltalk}
TBSMagritteReport subclass: #TBPostsReport
   instanceVariableNames: ''
   classVariableNames: ''
   category: 'TinyBlog-Components'
\end{displaycode}

Avec la m\'{e}thode \textcode{from:} nous disons que nous voulons cr\'{e}er un rapport en prenant les descriptions de n'importe quel blog. 

\begin{displaycode}{smalltalk}
TBPostsReport class >> from: aBlog
   | allBlogs |
   allBlogs := aBlog allBlogPosts.
   ^ self rows: allBlogs description: allBlogs anyOne magritteDescription
\end{displaycode}

On ajoute un rapport au composant admin \textcode{TBAdminComponent}.

\begin{displaycode}{smalltalk}
TBScreenComponent subclass: #TBAdminComponent
   instanceVariableNames: 'report'
   classVariableNames: ''
   category: 'TinyBlog-Components'
\end{displaycode}

\begin{displaycode}{smalltalk}
TBAdminComponent >> report
   ^ report

TBAdminComponent >> report: aReport
   report := aReport
\end{displaycode}

Comme le rapport est un composant fils du composant admin nous n'oublions pas de red\'{e}finir la m\'{e}thode \textcode{children} comme suit.

\begin{displaycode}{smalltalk}
TBAdminComponent >> children
   ^ super children copyWith: self report 
\end{displaycode}

La m\'{e}thode \textcode{initialize} permet d'initialiser la d\'{e}finition du rapport. Nous fournissons au composant \textcode{TBPostReport} l'acc\`{e}s aux donn\'{e}es.

\begin{displaycode}{smalltalk}
TBAdminComponent >> initialize
   super initialize.
   self report: (TBPostsReport from: self blog)
\end{displaycode}

Nous pouvons maintenant afficher le rapport.

\begin{displaycode}{smalltalk}
TBAdminComponent >> renderContentOn: html
   super renderContentOn: html.
   html tbsContainer: [
      html heading: 'Blog Admin'.
      html horizontalRule.
      html render: self report ]
\end{displaycode}

Par d\'{e}faut, le rapport affiche l'int\'{e}gralit\'{e} des donn\'{e}es pr\'{e}sentes dans chaque posts mais certaines colonnes ne sont pas utiles. Il faut donc filtrer les colonnes. Nous ne retiendrons ici que le titre, la cat\'{e}gorie et la date de r\'{e}daction.

Nous ajoutons une m\'{e}thode de classe pour la s\'{e}lection des colonnes et modifier ensuite la m\'{e}thode \textcode{from:} pour en tirer parti.

\begin{displaycode}{smalltalk}
TBPostsReport class >> filteredDescriptionsFrom: aBlogPost
   ^ aBlogPost magritteDescription select: [ :each | #(title category date) includes: each accessor selector ]
   
TBPostsReport class >> from: aBlog
   | allBlogs |
   allBlogs := aBlog allBlogPosts.
   ^ self rows: allBlogs description: (self filteredDescriptionsFrom: allBlogs anyOne)
\end{displaycode}
\subsection{Am\'{e}lioration des rapports}
Le rapport g\'{e}n\'{e}r\'{e} est brut. Il n'y a pas de titres sur les colonnes et l'ordre d'affichage des colonnes n'est pas fix\'{e} (il peut varier d'une instance \`{a} une autre). Pour g\'{e}rer cela, il suffit de modifier les descriptions Magritte pour chaque variable d'instance.

\begin{displaycode}{smalltalk}
TBPost >> descriptionTitle
   <magritteDescription>
   ^ MAStringDescription new
      label: 'Title';
      priority: 100;
      accessor: #title;
      beRequired;
      yourself
\end{displaycode}

\begin{displaycode}{smalltalk}
TBPost >> descriptionText
   <magritteDescription>
   ^ MAMemoDescription new
      label: 'Text';
      priority: 200;
      accessor: #text;
      beRequired;
      yourself
\end{displaycode}

\begin{displaycode}{smalltalk}
TBPost >> descriptionCategory
   <magritteDescription>
   ^ MAStringDescription new
      label: 'Category';
      priority: 300;
      accessor: #category;
      yourself
\end{displaycode}

\begin{displaycode}{smalltalk}
TBPost >> descriptionDate
   <magritteDescription>
   ^ MADateDescription new
      label: 'Date';
      priority: 400;
      accessor: #date;
      beRequired;
      yourself
\end{displaycode}

\begin{displaycode}{smalltalk}
TBPost >> descriptionVisible
   <magritteDescription>
   ^ MABooleanDescription new
      label: 'Visible';
      priority: 500;
      accessor: #visible;
      beRequired;
      yourself
\end{displaycode}

Identifiez-vous et vous devez obtenir la situation telle que repr\'{e}sent\'{e}e par la figure \ref{adminReportDraft}.

\begin{figure}

\begin{center}
\includegraphics[width=0.85\textwidth]{figures/ReportDraft.png}\caption{Administration avec un rapport.\label{adminReportDraft}}\end{center}
\end{figure}

\subsection{Gestion des posts}
Nous pouvons maintenant mettre en place un CRUD (Create Read Update Delete) permettant de g\'{e}rer les posts. Pour cela, nous allons ajouter une colonne (instance \textcode{MACommandColumn}) au rapport qui regroupera les diff\'{e}rentes op\'{e}rations utilisant \textcode{addCommandOn:}.

Ceci se fait lors de la cr\'{e}ation du rapport. En particulier nous donnons un acc\`{e}s au blog depuis le rapport. 

\begin{displaycode}{smalltalk}
TBSMagritteReport subclass: #TBPostsReport
	instanceVariableNames: 'report blog'
	classVariableNames: ''
	category: 'TinyBlog-Components'

TBSMagritteReport >> blog
   ^ blog

TBSMagritteReport >> blog: aTBBlog
   blog := aTBBlog
\end{displaycode}

\begin{displaycode}{smalltalk}
TBPostsReport class >> from: aBlog
	| report blogPosts |
	blogPosts := aBlog allBlogPosts.
	report := self rows: blogPosts description: (self filteredDescriptionsFrom: blogPosts anyOne).
	report blog: aBlog.
	report addColumn: (MACommandColumn new
		addCommandOn: report  selector: #viewPost: text: 'View'; yourself;
		addCommandOn: report selector: #editPost: text: 'Edit'; yourself;
		addCommandOn: report selector: #deletePost: text: 'Delete'; yourself).
	 ^ report
\end{displaycode}

L'ajout (add) est dissoci\'{e} des posts et se trouvera donc juste avant le rapport. Etant donn\'{e} qu'il fait partie du composant \textcode{TBPostsReport}, nous devons red\'{e}finir la m\'{e}thode \textcode{renderContentOn:} du composant \textcode{TBPostsReport} pour ins\'{e}rer le lien \textcode{add}.

\begin{displaycode}{smalltalk}
TBPostsReport >> renderContentOn: html
   html tbsGlyphIcon perform: #iconPencil.
   html anchor
      callback: [ self addPost ];
      with: 'Add post'.
   super renderContentOn: html
\end{displaycode}

Identifiez-vous \`{a} nouveau et vous devez obtenir la situation telle que repr\'{e}sent\'{e}e par la figure \ref{RapportNewLookActions}.

\begin{figure}

\begin{center}
\includegraphics[width=0.75\textwidth]{figures/RapportNewLookActions.png}\caption{Ajout d'un post.\label{RapportNewLookActions}}\end{center}
\end{figure}

\subsection{Impl\'{e}mentation des actions CRUD}
A chaque action (Create/Read/Update/Delete) correspond une m\'{e}thode de l'objet \textcode{TBPostsReport}. Nous allons maintenant les impl\'{e}menter. Un formulaire personnalis\'{e} est construit en fonction de l'op\'{e}ration demand\'{e} (il n'est pas utile par exemple d'avoir un bouton \symbol{34}Sauver\symbol{34} alors que l'utilisateur veut simplement lire le post).
\subsubsection{Ajouter un post}
\begin{displaycode}{smalltalk}
TBPostsReport >> renderAddPostForm: aPost
	^ aPost asComponent
		addDecoration: (TBSMagritteFormDecoration buttons: { #save -> 'Add post' .  #cancel -> 'Cancel'});
		yourself
\end{displaycode}

La m\'{e}thode \textcode{renderAddPostForm} illustre la puissance de Magritte pour g\'{e}n\'{e}rer des formulaires. 
Ici, le message \textcode{asComponent} envoy\'{e} \`{a} un objet m\'{e}tier instance de la classe \textcode{TBPost}, cr\'{e}\'{e} directement un composant Seaside. 
Nous ajoutons une d\'{e}coration \`{a} ce composant Seaside afin de g\'{e}rer ok/cancel.

\begin{displaycode}{smalltalk}
TBPostsReport >> addPost
	| post |
	post := self call: (self renderAddPostForm: TBPost new).
	post ifNotNil: [ blog writeBlogPost: post ]
\end{displaycode}

La m\'{e}thode \textcode{addPost} pour sa part, affiche le composant rendu par la m\'{e}thode \textcode{renderAddPostForm:} et lorsque qu'un nouveau post est cr\'{e}\'{e}, elle l'ajoute au blog.

Enregistrez-vous et vous devez obtenir la situation telle que repr\'{e}sent\'{e}e par la figure \ref{addAPost}.

\begin{figure}

\begin{center}
\includegraphics[width=0.75\textwidth]{figures/AddAPost.png}\caption{Ajout d'un post.\label{addAPost}}\end{center}
\end{figure}

\subsubsection{Editer un post}
\begin{displaycode}{smalltalk}
TBPostsReport >> renderEditPostForm: aPost
	^ aPost asComponent
		addDecoration: (TBSMagritteFormDecoration buttons: { #save -> 'Save post' . #cancel -> 'Cancel'});
		yourself

TBPostsReport >> editPost: aPost
	| post |
	post := self call: (self renderEditPostForm: aPost).
	post ifNotNil: [ blog save ]
\end{displaycode}
\subsubsection{Consulter un post}
\begin{displaycode}{smalltalk}
TBPostsReport >> viewPost: aPost
	self call: (self renderViewPostForm: aPost)

TBPostsReport >> renderViewPostForm: aPost
	^ aPost asComponent
		addDecoration: (TBSMagritteFormDecoration buttons: { #cancel -> 'Back' });
		yourself
\end{displaycode}
\subsubsection{Effacer un post}
Pour \'{e}viter une op\'{e}ration accidentelle, nous utilisons une boite modale pour que l'utilisateur confirme la suppression du post. Une fois le post effac\'{e}, la liste des posts g\'{e}r\'{e}s par le composant \textcode{TBPostsReport} est actualis\'{e}e et le rapport est rafra\^{i}chi.

\begin{displaycode}{smalltalk}
TBPostsReport >> deletePost: aPost
	(self confirm: 'Do you want remove this post ?') 
		ifTrue: [ blog removeBlogPost: aPost ]
\end{displaycode}

\begin{displaycode}{smalltalk}
TBBlog >> removeBlogPost: aPost
	posts remove: aPost ifAbsent: [ ].
	self save.
\end{displaycode}

Cette derni\`{e}re m\'{e}thode est \`{a} ajouter dans une classe de la partie m\'{e}tier de notre application. Il faut donc ajouter un test unitaire.

\begin{displaycode}{smalltalk}
TBBlogTest >> testRemoveBlogPost
	self assert: blog size equals: 1.
	blog removeBlogPost: blog allBlogPosts anyOne.
	self assert: blog size equals: 0
\end{displaycode}
\subsection{G\'{e}rer le probl\`{e}me du rafra\^{i}chissement des donn\'{e}es}
Les m\'{e}thodes \textcode{TBPostsReport \textgreater{}\textgreater{} addPost:} et \textcode{TBPostsReport \textgreater{}\textgreater{} deletePost:} font bien leur travail mais les donn\'{e}es \`{a} l'\'{e}cran ne sont pas mises \`{a} jour. Il faut donc rafraichir la liste des posts car il y a un d\'{e}calage entre les donn\'{e}es en m\'{e}moire et celles stock\'{e}es dans la base de donn\'{e}es.

\begin{displaycode}{smalltalk}
TBPostsReport >> refreshReport
	self rows: blog allBlogPosts.
	self refresh.
\end{displaycode}

\begin{displaycode}{smalltalk}
TBPostsReport >> addPost
	| post |
	post := self call: (self renderAddPostForm: TBPost new).
	post ifNotNil: [
		blog writeBlogPost: post.
 		self refreshReport
	]

TBPostsReport >> deletePost: aPost
	(self confirm: 'Do you want remove this post ?') 
		ifTrue: [ blog removeBlogPost: aPost.
				 self refreshReport ]
\end{displaycode}

Le formulaire est fonctionnel maintenant et g\`{e}re m\^{e}me les contraintes de saisie c'est-\`{a}-dire que le formulaire assure par exemple que les champs d\'{e}clar\'{e}s comme obligatoire dans les descriptions Magritte sont bien renseign\'{e}s. 
\subsection{Am\'{e}lioration de l'apparence du formulaire}
Pour tirer partie de Bootstrap, nous allons modifier les d\'{e}finitions Magritte. Tout d'abord, sp\'{e}cifions que le rendu du formulaire doit se baser sur Bootstrap.

\begin{displaycode}{smalltalk}
TBPost >> descriptionContainer
	<magritteContainer>
	^ super descriptionContainer
		componentRenderer: TBSMagritteFormRenderer;
		yourself
\end{displaycode}

Nous pouvons maintenant nous occuper des diff\'{e}rents champs de saisie et am\'{e}liorer leur apparence.

\begin{displaycode}{smalltalk}
TBPost >> descriptionTitle
	<magritteDescription>
	^ MAStringDescription new
		label: 'Title';
		priority: 100;
		accessor: #title;
		requiredErrorMessage: 'A blog post must have a title.';
		comment: 'Please enter a title';
		componentClass: TBSMagritteTextInputComponent;
		beRequired;
		yourself
\end{displaycode}

\begin{displaycode}{smalltalk}
TBPost >> descriptionText
	<magritteDescription>
	^ MAMemoDescription new
		label: 'Text';
		priority: 200;
		accessor: #text;
		beRequired;
		requiredErrorMessage: 'A blog post must contain a text.';
		comment: 'Please enter a text';
		componentClass: TBSMagritteTextAreaComponent;
		yourself
\end{displaycode}

\begin{displaycode}{smalltalk}
TBPost >> descriptionCategory
	<magritteDescription>
	^ MAStringDescription new
		label: 'Category';
		priority: 300;
		accessor: #category;
		comment: 'Unclassified if empty';
		componentClass: TBSMagritteTextInputComponent;
		yourself
\end{displaycode}

\begin{displaycode}{smalltalk}
TBPost >> descriptionVisible
	<magritteDescription>
	^ MABooleanDescription new
		checkboxLabel: 'Visible';
		priority: 500;
		accessor: #visible;
		componentClass: TBSMagritteCheckboxComponent;
		beRequired;
		yourself
\end{displaycode}

Le formulaire d'\'{e}dition d'un post doit maintenant ressembler \`{a} celui de la figure \ref{addAPostBootstrap}.

\begin{figure}

\begin{center}
\includegraphics[width=0.85\textwidth]{figures/AddAPostBootstrap.png}\caption{Formulaire d'ajout d'un post avec Bootstrap.\label{addAPostBootstrap}}\end{center}
\end{figure}

\section{Gestion de Session}
Un objet session est attribu\'{e} \`{a} chaque instance de l'application. Il permet de conserver principalement des informations qui sont partag\'{e}es et accessible entre les composants. Une session est pratique pour g\'{e}rer les informations de l'utilisateur en cours (identifi\'{e}).  Nous allons voir comment nous l'utilisons pour g\'{e}rer une connexion.

L'administrateur du blog peut vouloir voyager entre la partie priv\'{e}e et la partie publique de TinyBlog. 

Nous d\'{e}finissons une nouvelle sous-classe de \textcode{WASession} nomm\'{e}e \textcode{TBSession}. Pour savoir si l'utilisateur s'est authentifi\'{e}, nous devons d\'{e}finir un objet session et ajouter une variable d'instance contenant une valeur bool\'{e}enne pr\'{e}cisant l'\'{e}tat de l'utilisateur. 

\begin{displaycode}{smalltalk}
WASession subclass: #TBSession
	instanceVariableNames: 'logged'
	classVariableNames: ''
	category: 'TinyBlog-Components'

TBSession >> logged
	^ logged

TBSession >> logged: anObject
	logged := anObject

TBSession >> isLogged
	^ self logged
\end{displaycode}

Il faut ensuite initialiser \`{a} `false` cette variable d'instance \`{a} la cr\'{e}ation d'une session.

\begin{displaycode}{smalltalk}
TBSession >> initialize
	super initialize.
	self logged: false.
\end{displaycode}

Dans la partie priv\'{e}e de TinyBlog, ajoutons un lien permettant le retour \`{a} la partie publique. Nous utilisons ici le message \textcode{answer} puisque le composant d'administration a \'{e}t\'{e} appel\'{e} \`{a} l'aide du message \textcode{call:}.

\begin{displaycode}{smalltalk}
TBAdminComponent >> renderContentOn: html
	super renderContentOn: html.
	html tbsContainer: [
		html heading: 'Blog Admin'.
		html tbsGlyphIcon perform: #iconEyeOpen.
		html anchor
			callback: [ self answer ];
			with: 'Public Area'.
		html horizontalRule.
		html render: self report.
	]
\end{displaycode}

Dans l'espace public, il nous faut modifier le comportement du lien permettant d'acc\'{e}der \`{a} l'administration. Il doit provoquer l'affichage de la boite d'authentification uniquement si l'utilisateur ne s'est pas encore connect\'{e}.

\begin{displaycode}{smalltalk}
TBPostsListComponent >> renderSignInOn: html
	self session isLogged
		ifFalse: [
			html tbsGlyphIcon perform: #iconLock.
			html html: '<a data-toggle="modal" href="#myAuthDialog" class="link">SignIn</a>' ]
		ifTrue: [
			html tbsGlyphIcon perform: #iconUser.
			html anchor callback: [ self gotoAdministration ]; with: 'Private area' ]
\end{displaycode}

Enfin, le composant \textcode{TBAuthentificationComponent} doit mettre \`{a} jour la variable d'instance \textcode{logged} de la session si l'utilisateur est bien un administrateur.

\begin{displaycode}{smalltalk}
TBauthentificationComponent >> validate
	(self account = 'admin' and: [ self password = 'password' ]) 
		ifTrue: [ self session logged: true.
		component gotoAdministration ]
\end{displaycode}

Il vous faut maintenant sp\'{e}cifier \`{a} Seaside qu'il doit utiliser l'objet \textcode{TBSession} comme objet de session courant pour l'application TinyBlog. 
Cette initialisation s'effectue dans la m\'{e}thode \textcode{initialize} de la classe \textcode{TBApplicationRootComponent} que l'on modifie ainsi:

\begin{displaycode}{smalltalk}
TBApplicationRootComponent class >> initialize
      "self initialize"
      | app |
      app := WAAdmin register: self asApplicationAt: 'TinyBlog'.
      app 
         preferenceAt: #sessionClass put: TBSession.
      app
         addLibrary: JQDeploymentLibrary;
         addLibrary: JQUiDeploymentLibrary;
         addLibrary: TBSDeploymentLibrary
\end{displaycode}

Pensez \`{a} ex\'{e}cuter cette m\'{e}thode via \textcode{TBApplicationRootComponent initialize} avant de tester \`{a} nouveau l'application.
\section{Am\'{e}liorations possibles}
\begin{itemize}
\item Ajouter un bouton \symbol{34}D\'{e}connexion\symbol{34}
\item g\'{e}rer plusieurs comptes administrateur ce qui n\'{e}cessite une am\'{e}lioration des sessions qui devront conserver l'identification de l'utilisateur courant
\item g\'{e}rer plusieurs blogs 
\end{itemize}


\ifx\wholebook\relax\else
\end{document}
\fi