% -*- 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 = {TinyBlog: A Simple Teapot Web Interface}, Author = {}, Keywords = {pharo, smalltalk}}} \begin{document} \fi \chapter{TinyBlog: A Simple Teapot Web Interface}\section{Previous Week Solution} You can load the solution of the previous week using the following snippet: \begin{displaycode}{smalltalk} Gofer new smalltalkhubUser: 'PharoMooc' project: 'TinyBlog'; package: 'ConfigurationOfTinyBlog'; load. #ConfigurationOfTinyBlog asClass loadWeek2Correction \end{displaycode} After a loading a package, you shall run the unit tests to ensure that the loaded code is correctly working. Open the TestRunner (World menu \textgreater{} Test Runner), find the 'TinyBlog-Tests' package and run all unit tests of the \textcode{TBBlogTest} class by clicking on the 'Run Selected' button. All tests should be green. One alternative is to press the green icon on the side of the class \textcode{TBBlogTest}. Open a code browser to look at the code of both classes \textcode{TBBlog} and \textcode{TBBlogTest}. You can now complete you own implementation if needed. Before continuing, do not forget to commit a new version in your repository on Smalltalkhub or SS3 if you modified your code. \section{A Web Interface for TinyBlog with Teapot} This week, we will create a first simple web interface for TinyBlog with Teapot (\url{http://smalltalkhub.com/#!/~zeroflag/Teapot}). We will implement a more complete version with Seaside next week. \section{The TBTeapotWebApp Class} Create a new class named \textcode{TBTeapotWebApp}: \begin{displaycode}{smalltalk} Object subclass: #TBTeapotWebApp instanceVariableNames: 'teapot' classVariableNames: 'Server' package: 'TinyBlog-Teapot' \end{displaycode} The variable \textcode{teapot} will refer to a little Teapot HTTP server. Here we use a different implementation of the Singleton Design Pattern by using a class variable named \textcode{Server}. We use a Singleton to avoid to have two servers listening to the same port. Add the instance method \textcode{initialize} to initialize the instance variable \textcode{teapot}: \begin{displaycode}{smalltalk} TBTeapotWebApp >> initialize super initialize. teapot := Teapot configure: { #port -> 8081. #debugMode -> true }. \end{displaycode} \subsection{The Home Page} The \textcode{homePage} method defined inside in the 'html' protocol should return the HTML code of the home page of our web application as a String. Let's start with a simple version: \begin{displaycode}{smalltalk} TBTeapotWebApp >> homePage ^ '

TinyBlog Web App

' \end{displaycode} \subsection{Declare Routes} Add a \textcode{start} method to declare to the \textcode{teapot} object the URLs it must answer to. So far, we only add the route \textcode{/} accessed via a GET Http method: \begin{displaycode}{smalltalk} TBTeapotWebApp >> start teapot GET: '/' -> [ self homePage ]; start \end{displaycode} \subsection{Stop the Application} Add also a method \textcode{stop} to stop the application. \begin{displaycode}{smalltalk} TBTeapotWebApp >> stop teapot stop \end{displaycode} \subsection{Starting the application} Add two class-side methods \textcode{start} and \textcode{stop} to start and stop the web application in the protocol 'start/stop'. These two methods use the class variable \textcode{Server} to implement a Singleton. \begin{displaycode}{smalltalk} TBTeapotWebApp class >> start Server ifNil: [ Server := self new start ] TBTeapotWebApp class >> stop Server ifNotNil: [ Server stop. Server := nil ] \end{displaycode} \section{Test your Application} Execute the following snippet to start your application: \begin{displaycode}{smalltalk} TBTeapotWebApp start \end{displaycode} In a web browser, try to access the application with this URL: \url{http://localhost:8081/}. You should see the text: \symbol{34}TinyBlog Web App\symbol{34} as in Figure \ref{fighome}. \begin{figure} \begin{center} \includegraphics[width=0.6\textwidth]{figures/homepage.png}\caption{A first page served by our application.\label{fighome}}\end{center} \end{figure} \section{Display the List of All Visible Posts} Modify now the code of the \textcode{homePage} method to display the list of all visible posts in the current blog. Remember these posts can be obtained with: \textcode{TBBlog current allVisibleBlogPosts}. We implement that functionality by adding three methods and modifying the \textcode{homePage} method. \begin{displaycode}{smalltalk} TBTeapotWebApp >> allPosts ^ TBBlog current allVisibleBlogPosts \end{displaycode} Since we need to generate a long String that contains the HTML code of the home page, we decided to use a Stream in the \textcode{homePage} method. We also factored out the HTML generation of the HTML page header and footer in two different methods: \textcode{renderPageHeaderOn:} and \textcode{renderPageFooterOn:}. \begin{displaycode}{smalltalk} TBTeapotWebApp >> homePage ^ String streamContents: [ :s | self renderPageHeaderOn: s. s << '

TinyBlog Web App

'. s << ''. self renderPageFooterOn: s. ] \end{displaycode} Note that the message \textcode{\textless{}\textless{}} is a different name for the message \textcode{nextPutAll:} that adds a collection of elements to a stream. \begin{displaycode}{smalltalk} TBTeapotWebApp >> renderPageHeaderOn: aStream aStream << '' TBTeapotWebApp >> renderPageFooterOn: aStream aStream << '' \end{displaycode} Test your application in a web browser, you should now see a list of post titles as in Figure \ref{figposts}. If this is not the case make sure that your blog contains some post. You can use the message \textcode{createDemoPosts} to add some generic blog posts. \begin{displaycode}{smalltalk} TBBlog createDemoPosts \end{displaycode} \begin{figure} \begin{center} \includegraphics[width=0.6\textwidth]{figures/withPosts.png}\caption{Showing post titles.\label{figposts}}\end{center} \end{figure} \section{Details of a Post}\subsection{Add a New Web Page} We would like that the following URL \url{http://localhost:8081/post/1} displays the whole post number 1. To start, let us think about the worst case and define what should happen in case of errors. We define the method \textcode{errorPage}. \begin{displaycode}{smalltalk} TBTeapotWebApp >> errorPage ^ String streamContents: [ :s | self renderPageHeaderOn: s. s << '

Oups, an error occurred

'. self renderPageFooterOn: s ] \end{displaycode} Teapot supports patterns such as '\textless{}id\textgreater{}' in route definitions. The corresponding value of '\textless{}id\textgreater{}' in the incoming URL is then accessible through the \textcode{request} object passed as a block parameter. Now we modify the \textcode{start} method and introduce a new route into the application to display the content of a post. \begin{displaycode}{smalltalk} TBTeapotWebApp >> start teapot GET: '/' -> [ self homePage ]; GET: '/post/' -> [ :request | self pageForPostNumber: (request at: #id) asNumber ]; start \end{displaycode} We now add a new method named \textcode{pageForPostNumber:} displaying the whole content of a 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} You can now test your application directly with the following URL: \url{http://localhost:8081/post/1} The parameter of \textcode{pageForPostNumber:} is the integer passed in the URL and it is used as an index to retrieve the post to display in the collection of posts. Obviously, this is a fragile solution because if the order of the posts changes in the collection, a given URL will not display the same post as before. \subsection{Add Links to Posts} Modify the \textcode{homePage} method so that post titles in the list will be links to their own web page. \begin{displaycode}{smalltalk} TBTeapotWebApp >> homePage ^ String streamContents: [ :s | self renderPageHeaderOn: s. s << '

TinyBlog Web App

'. s << ''. self renderPageFooterOn: s. ] \end{displaycode} Now, the home page of the application displays a list of clickable post titles and if you click on a post title, you will see the content of this post. \section{Possible Extensions} This application is a really simple and pedagogical example through which you manipulate collections, streams, etc. You can improve this web application and implement new functionalities such as: \begin{itemize} \item adding a return to home page link on a post page, \item adding a new page that displays the list of all post categories, \item adding a new page that displays all posts that belong to one specific category, \item adding CSS styles to make this web application more appealing. \end{itemize} \ifx\wholebook\relax\else \end{document} \fi