% -*- 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 = {Construire une interface Web avec Teapot pour TinyBlog},
Author = {},
Keywords = {pharo, smalltalk}}}
\begin{document}
\fi
\chapter{Une Interface Web avec Teapot pour TinyBlog}\section{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 loadWeek2Correction
\end{displaycode}
Apr\`{e}s le chargement d'un package, il est recommand\'{e} d'ex\'{e}cuter les tests unitaires qu'il contient afin de v\'{e}rifier le bon fonctionnement du code charg\'{e}.
Pour cela, vous pouvez lancer l'outil TestRunner (World menu \textgreater{} Test Runner), chercher le package TinyBlog-Tests et lancer tous les tests unitaires de la classe \textcode{TBBlogTest} en cliquant sur le bouton \symbol{34}Run Selected\symbol{34}. Tous les tests doivent \^{e}tre verts. Une alternative est de presser l'icone verte qui se situe \`{a} cot\'{e} de la class \textcode{TBBlogTest}.
Ouvrez maintenant un browser de code pour regarder le code des classes \textcode{TBBlog} et \textcode{TBBlogTest} et compl\'{e}ter votre propre code si n\'{e}cessaire.
Avant de poursuivre, n'oubliez pas de committer une nouvelle version dans votre d\'{e}p\^{o}t sur Smalltalkhub ou SS3 si vous avez modifi\'{e} votre application.
\section{Une interface web avec Teapot}
Dans la suite, nous allons cr\'{e}er une premi\`{e}re interface web pour TinyBlog en utilisant Teapot (\url{http://smalltalkhub.com/#!/~zeroflag/Teapot}). Nous verrons une solution bien plus compl\`{e}te avec Seaside par la suite.
\subsection{La classe TBTeapotWebApp}
Cr\'{e}er une classe nomm\'{e}e \textcode{TBTeapotWebApp} ainsi:
\begin{displaycode}{smalltalk}
Object subclass: #TBTeapotWebApp
instanceVariableNames: 'teapot'
classVariableNames: 'Server'
package: 'TinyBlog-Teapot'
\end{displaycode}
La variable \textcode{teapot} contiendra un petit server HTTP Teapot.
Ici on utilise une impl\'{e}mentation diff\'{e}rente du Design Pattern Singleton en utilisant une variable de classe nomm\'{e}e \textcode{Server}. Nous faison cela afin de ne pas avoir deux serveurs g\'{e}rant les connexions sur le m\^{e}me port.
Ajouter la m\'{e}thode d'instance \textcode{initialize} pour initialiser la variable d'instance \textcode{teapot} :
\begin{displaycode}{smalltalk}
TBTeapotWebApp >> initialize
super initialize.
teapot := Teapot configure: {
#port -> 8081.
#debugMode -> true }.
\end{displaycode}
\subsection{La Page d'accueil}
D\'{e}finissons une m\'{e}thode \textcode{homePage} dans le protocol 'html' qui retourne le code HTML de la page d'accueil de notre application web. Commen\c{c}ons par une version simple :
\begin{displaycode}{smalltalk}
TBTeapotWebApp >> homePage
^ '
TinyBlog Web App
'
\end{displaycode}
\subsection{D\'{e}clarer les URLs (routes)}
Ajouter maintenant une m\'{e}thode \textcode{start} pour que l'objet teapot r\'{e}ponde \`{a} des URLs particuli\`{e}res. Commen\c{c}ons par r\'{e}pondre \`{a} l'URL \textcode{/} lorsqu'elle est acc\'{e}d\'{e}e en GET :
\begin{displaycode}{smalltalk}
TBTeapotWebApp >> start
"a get / is now returning an html welcome page"
teapot
GET: '/' -> [ self homePage ];
start
\end{displaycode}
\subsection{Stopper l'application}
Ajouter \'{e}galement une m\'{e}thode pour stopper l'application :
\begin{displaycode}{smalltalk}
TBTeapotWebApp >> stop
teapot stop
\end{displaycode}
\subsection{D\'{e}marrage l'application }
Ajouter deux m\'{e}thodes \textcode{start} et \textcode{stop} c\^{o}t\'{e} classe pour respectivement d\'{e}marrer et arr\^{e}ter l'application dans le protocol 'start/stop'. Ces m\'{e}thodes utilisent la variable de class \textcode{Server} pour implanter un Singleton.
\begin{displaycode}{smalltalk}
TBTeapotWebApp class >> start
Server ifNil: [ Server := self new start ]
\end{displaycode}
\begin{displaycode}{smalltalk}
TBTeapotWebApp class >> stop
Server ifNotNil: [ Server stop. Server := nil ]
\end{displaycode}
\begin{figure}
\begin{center}
\includegraphics[width=0.6\textwidth]{figures/homepage.png}\caption{Une premi\`{e}re page servie par notre application.\label{fighome}}\end{center}
\end{figure}
\section{Tester votre application}
Maintenant nous pouvons lancer notre application en ex\'{e}cutant le code suivant pour d\'{e}marrer votre application :
\begin{displaycode}{smalltalk}
TBTeapotWebApp start
\end{displaycode}
Avec un navigateur web, vous pouvez acc\'{e}der \`{a} l'application via l'URL \url{http://localhost:8081/}. Vous devriez voir s'afficher le texte \symbol{34}TinyBlog Web App\symbol{34} comme la figure \ref{fighome}.
\begin{figure}
\begin{center}
\includegraphics[width=0.6\textwidth]{figures/withPosts.png}\caption{Afficher la liste des titres de posts.\label{figposts}}\end{center}
\end{figure}
\section{Afficher la liste des posts}
On souhaite maintenant modifier le code de la m\'{e}thode \textcode{homePage} pour que la page d'accueil affiche la liste de tous les posts visibles.
Pour rappel, tous les posts peuvent \^{e}tre obtenus via \textcode{TBBlog current allVisibleBlogPosts}.
Ajoutons une m\'{e}thode d'acc\`{e}s aux posts dans le protocol 'accessing' et modifions la m\'{e}thode \textcode{homePage} ainsi que deux petites m\'{e}thodes auxilliaires.
\begin{displaycode}{smalltalk}
TBTeapotWebApp >> allPosts
^ TBBlog current allVisibleBlogPosts
\end{displaycode}
Comme il faut g\'{e}n\'{e}rer une longue chaine de caract\`{e}res contenant le code HTML de cette page, nous utilisons un fl\^{o}t (stream) dans la m\'{e}thode \textcode{homePage}.
Nous avons \'{e}galement d\'{e}coup\'{e} le code en plusieurs m\'{e}thodes dont \textcode{renderPageHeaderOn:} et \textcode{renderPageFooterOn:} qui permettent de g\'{e}n\'{e}rer l'en-t\^{e}te et le pieds de la page html.
\begin{displaycode}{smalltalk}
TBTeapotWebApp >> homePage
^ String streamContents: [ :s |
self renderPageHeaderOn: s.
s << 'TinyBlog Web App
'.
s << ''.
self allPosts do: [ :aPost |
s << ('- ', aPost title, '
') ].
s << '
'.
self renderPageFooterOn: s.
]
\end{displaycode}
Notez que le message \textcode{\textless{}\textless{}} est un synonyme du message \textcode{nextPutAll:} qui ajoute une collection d'\'{e}l\'{e}ments dans un fl\^{o}t (stream).
\begin{displaycode}{smalltalk}
TBTeapotWebApp >> renderPageHeaderOn: aStream
aStream << ''
\end{displaycode}
\begin{displaycode}{smalltalk}
TBTeapotWebApp >> renderPageFooterOn: aStream
aStream << ''
\end{displaycode}
Tester l'application dans un navigateur web, vous devez maintenant voir la liste des titres des posts comme dans la figure \ref{figposts}. Si ce n'est pas le cas, assurez vous que votre blog a bien des posts. Vous pouvez utiliser le message \textcode{createDemoPosts} pour ajouter quelques postes g\'{e}n\'{e}riques.
\begin{displaycode}{smalltalk}
TBBlog createDemoPosts
\end{displaycode}
\section{D\'{e}tails d'un Post}\subsection{Ajouter une Nouvelle Page}
Am\'{e}liorons notre application. On souhaite que l'URL \url{http://localhost:8081/post/1} permette de voir le post num\'{e}ro 1.
Commen\c{c}ons par penser au pire, et d\'{e}finissons une m\'{e}thode pour les erreurs.
Nous d\'{e}finissons la m\'{e}thode \textcode{errorPage} comme suit :
\begin{displaycode}{smalltalk}
TBTeapotWebApp >> errorPage
^ String streamContents: [ :s |
self renderPageHeaderOn: s.
s << 'Oups, an error occurred
'.
self renderPageFooterOn: s.
]
\end{displaycode}
Teapot permet de d\'{e}finir des routes avec des patterns comme '\textless{}id\textgreater{}' dont la valeur est ensuite accessible dans l'objet requ\^{e}te re\c{c}u en param\`{e}tre du bloc.
Nous modifions donc la m\'{e}thode \textcode{start} pour ajouter une nouvelle route \`{a} notre application permettant d'afficher le contenu d'un post.
\begin{displaycode}{smalltalk}
TBTeapotWebApp >> start
teapot
GET: '/' -> [ self homePage ];
GET: '/post/' -> [ :request | self pageForPostNumber: (request at: #id) asNumber ];
start
\end{displaycode}
Il faut maintenant d\'{e}finir la m\'{e}thode \textcode{pageForPostNumber:} qui affiche toutes les informations d'un post:
\begin{displaycode}{smalltalk}
TBTeapotWebApp >> pageForPostNumber: aPostNumber
|currentPost|
currentPost := self allPosts at: aPostNumber ifAbsent: [ ^ self errorPage ].
^ String streamContents: [ :s |
self renderPageHeaderOn: s.
s << ('', currentPost title, '
').
s << ('', currentPost date mmddyyyy, '
').
s << (' Category: ', currentPost category, '
').
s << ('', currentPost text, '
').
self renderPageFooterOn: s.
]
\end{displaycode}
Dans le code ci-dessus, on peut voir que le nombre pass\'{e} dans l'URL est utilis\'{e} comme la position du post \`{a} afficher dans la collection des posts.
Cette solution est simple mais fragile puisque si l'ordre des posts dans la collection change, une m\^{e}me URL ne d\'{e}signera plus le m\^{e}me post.
\subsection{Ajouter des liens vers les posts}
Modifions la m\'{e}thode \textcode{homePage} pour que les titres des posts soient des liens vers leur page respective.
\begin{displaycode}{smalltalk}
TBTeapotWebApp >> homePage
^ String streamContents: [ :s |
self renderPageHeaderOn: s.
s << 'TinyBlog Web App
'.
s << ''.
self renderPageFooterOn: s
]
\end{displaycode}
Maintenant, la page d'accueil de l'application affiche bien une liste de lien vers les posts.
\section{Extensions possibles}
Cette application est un exemple p\'{e}dagogique \`{a} travers lequel vous avez manipul\'{e} des collections, des fl\^{o}ts (Streams), etc.
Plusieurs \'{e}volutions peuvent \^{e}tre apport\'{e}es telles que:
\begin{itemize}
\item sur la page de d\'{e}tails d'un post, ajouter un lien pour revenir \`{a} la page d'accueil,
\item ajouter une page affichant la liste cliquable des cat\'{e}gories de posts,
\item ajouter une page affichant tous les posts d'une cat\'{e}gorie donn\'{e}e,
\item ajouter des styles CSS pour avoir un rendu plus agr\'{e}able.
\end{itemize}
\ifx\wholebook\relax\else
\end{document}
\fi