You can load the solution of the previous week using the following snippet:
Make sure that you Teapot server is stopped.
The work presented in this exercise does not require the one on Voyage and MongoDB.
This week, we start with a web UI dedicated to display posts to users. Next week, we will develop another web UI for the blog owner to admnistrate the posts. In both cases, we will define Seaside components http://www.seaside.st which has a freely available book online here: http://book.seaside.st.
There are two ways to start Seaside. The first one consists in executing the following code:
The second one uses the graphical tool named "Seaside Control Panel" (World Menu>Tools>Seaside Control Panel).
In the contextual menu (right clic) of this tool, select "add adaptor..." and add a server of type
ZnZincServerAdaptor, then define the port number (e.g. 8080) it should run on (cf. figure 55.1). By opening a web browser on the URL http://localhost:8080, you should see the Seaside home page as displayed on Figure 55.2.
Create a class named
TBApplicationRootComponent which will be the entry point of the application.
On class-side, we implement the
initialize method in the
'initialization' protocol to declare the application to Seaside. We also integrate dependencies to the Bootstrap framework (CSS and JS files will be embedded in the application).
Once declared, you should execute this method with
TBApplicationRootComponent initialize. Indeed, class-side
initialize methods are executed at loading-time of a class but since the class already exists, we must execute it by hand.
We also add a method named
canBeRoot to specify that
TBApplicationRootComponent is not a simple Seaside component but a complete application. This component will be automatically instantiated when a user connects to the application.
You can verify that your application is correctly registered by Seaside by connecting to the Seaside server through your web browser, click on "Browse the applications installed in your image" and then see that TinyBlog appears in the list.
Let's add an instance method named
rendering protocol to make our application displaying something.
You can customize the web page header and declare it as HTML 5 compliant by redefining the
TBApplicationRootComponent component is the root component of our application. It will not display a lot of things but will contain and display other components. For example, a component to display posts to the blog readers, a component to administrate the blog and its posts, ... Therefore, we decided that the
TBApplicationRootComponent component will contain components inheriting from the abstract class named
TBScreenComponent that we will now define.
We are now ready to define multiple visual components for our application. The first chapters of http://book.seaside.st can help you and give more details than this tutorial if needed.
Figure 55.5 shows the different components to develop and where they are taking place.
All components contained in
TBApplicationRootComponent will be subclasses of the abstract class
TBScreenComponent. This class allows us to factorize shared behaviors between all our components.
All components need to access the model of our application. Therefore, in the 'accessing' protocol, we add a
blog method that returns an instance of
TBBlog (here the singleton).
Inspect now the blog object returned by
TBBlog current and verify that it contains some posts. If it does not, execute:
TBBlog current createDemoPost.
In the future, if you want to manage multiple blogs, you will need to modify this method and use information stored in the active session to retrieve the current user/blog (cf.
TBSession later on).
The Bootstrap library is accessible from Seaside as we will see. If you access the Seaside Bootstrap application in your web browser (bootstrap link in the list of Seaside application or directly access http://localhost:8080/bootstrap) you can see several examples as shown in figure 55.6.
If you click on the Examples link at the bottom of the web page you can see some graphical elements and their respective code to integrate them in your application (cf. figure 55.7).
The Seaside Bootstrap library code and documentation is available here: http://smalltalkhub.com/#!/~TorstenBergmann/Bootstrap. There is also an online demo here: http://pharo.pharocloud.com/bootstrap. This library is already loaded in the PharoWeb image used in this tutorial.
Let's define a component to display the top of all components in our application.
The 'rendering' protocol contains the
renderContentOn: method that displays our application header.
Our header is a simple navigation bar of Bootstrap (See Figure 55.8)
By default, in Bootstrap navigation bars, there is a link on tbsNavbarBrand. Since we consider that useless in our application, we used a
'#' anchor. Now, when the user click on the title link, nothing happen.
Usually, clicking on an application title bring back the user to the web application home page.
It is not desirable to instantiate the
TBHeaderComponent each time a component is called.
So, we add an instance variable named
header in the
TBScreenComponent component and initializes it in its
In Seaside, subcomponents of a component must be returned by the composite when sending it the
So, we must define that the
TBHeaderComponent instance is a children of the
TBScreenComponent component in the Seaside component hierachy (and not in the Pharo classes hierarchy). We do so by specializing the method
renderContentOn: method ('rendering' protocol), we can now display the subcomponent (the header):
Temporarily, we will directly use the
TBScreenComponent while we develop other components.
So, we instantiate it in
initialize method and store it in the instance variable
We declare the containment relationship by returning
main as children of
If you refresh your application in a web browser, you should obtain what is depicted on Figure 55.8.
The blog name should be customizable using an instance vairable in the
TBBlog class and the application header component should display this title.
We will now display the list of all posts - which is the primary goal in fact. Remember that we speak about the public access to the blog here and not the administration interface that will be developped later.
Let's create a
TBPostsListComponent inheriting from
Add a temporary
renderContentOn: method (in the 'rendering' protocole) to test during development (cf. Figure 55.9).
Add this new component in the root application component:
Modify this method is not a good practice so we add a setter method to dynamically change the component to display in the future.
But, we keep that by default the
TBPostsListComponent component will be displayed.
Now e will define
TBPostComponent to display the deatils of a Post.
Each post will be graphically displayed by an instance of
TBPostComponent which will show the post title, its date and its content.
renderContentOn: method defines the HTML rendering of a post.
Next week, we will develop the administration web UI using Magritte and this will demonstrate that it is not common at all to manually develop components as we explained above. Indeed, Magritte is used to describe model data and then offers automatic Seaside component generators. As we will see, all the code of the previous section could be done just with:
To display all visible posts in the database, we just need to modify the
TBPostsListComponent >> renderContentOn: method:
Refresh you web browser and you should get ... the following error:
Not Found /TinyBlog
By default, when an error occurs in a web application, Seaside returns an HTML page that contains a generic message. You can change this message or during development, you can configure Seaside to open a debugger directly in Pharo IDE. To configure Seaside, just execute the following snippet:
Now, if you refresh the web page in your browser, a debugger should open on Pharo side. If you analyze the stack, you should see that we forgot to define the following method:
The web application should now correctly renders and you obtains what is shown in Figure 55.10.
Let's use Bootstrap to make the list of posts more beautiful using a Bootstrap container.
Yous web application whould look like Figure 55.11.
Posts are classified into categories. By default, a post is classified into a special category named "Unclassified" if nothing is specified.
We will now create a new component named
TBCategoriesComponent to manage the list of categories.
TBCategoriesComponent is responsible to display the list of all categories available in the database (the model) and also to select one category. This component should be able to communicate with the
TBPostsListComponent to pass it the currently selected category.
On class-side, we define a creation method.
selectCategory: method (protocol 'action') communicates the currently selected category to the
TBPostsListComponent, we now add an instance variable to store the current category.
We add a rendering method ('rendering' protocol) to display one category. A category is rendered as a link and that make this category the current one if the user click on it.
renderContentOn: method of the
TBCategoriesComponent is now straighforward, it just iterates over all categories and renders them using bootstrap brushes:
It remains to display the list of categories in the root application component and refresh the list of displayed posts regarding the currently selected category.
We have to modify some methods of
readSelectedPosts method returns the posts to display.
If the current category is
nil, it means that the user did not select any category yet and all visible posts of the database are displayed.
If the user has selected a category, only posts of this category are displayed.
Then, the method that renders the list of posts can be modified as follows:
An instance of
TBCategoriesComponent is rendered on the web page and allows users to select a category (See Figure 55.12).
We will improve the layout of
TBPostsListComponent using a repsonsive design for the list of posts.
It means that the CSS will adapt the component size and placement based on the available space.
Components are displayed inside two Bootstrap containers in a row and two columns. Column width is determined according to the resolution (viewport) of the displaying device. The 12 columns of the Bootstrap grid are splitted between the list of categories and list of posts. If a low resolution is used, the list of categories will be above the list of posts (each lists will occupy 100% of the width of the container).
You should now obtain the same look as in Figure 55.13.
When you select a category, the list of posts is correctly refreshed. However, the current category is not highlighted. To introduce this feature, we modify the following method:
The application works well but you must not keep the current implementation of
TBPostsListComponent >> renderContentOn:.
This method is too long and cannot be easily reused. Propose a solution.
We are now ready to define a administrative UI for TinyBlog.
For example, you can:
Figure 55.14 shows the final application you may have developped.