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 renderContentOn:
in rendering
protocol to make our application displaying something.
If you open http://localhost:8080/TinyBlog in your web browser, the displayed a web should look like to the one on figure 55.4.
You can customize the web page header and declare it as HTML 5 compliant by redefining the updateRoot:
method.
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 initialize
method.
In Seaside, subcomponents of a component must be returned by the composite when sending it the children
message.
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 children
.
In the 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 main
.
We declare the containment relationship by returning main
as children of TBApplicationRootComponent
.
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 TBScreenComponent
:
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.
The 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.
The selectCategory:
method (protocol 'action') communicates the currently selected category to the TBPostsListComponent
.
In 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.
Finally, the 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 TBPostsListComponent
.
First, the 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.