You can load the solution of the previous week using the following snippet:
To test the code, you should start the Seaside HTTP server using the Seaside Control Panel tool (cf. previous week) or directly execute the following code:
You might also need to create some posts:
Before continuing, stop the Teapot server:
Magritte is a library to describe data. Using Magritte descriptions, you can then generate various representations for your data or operations such as requests. Combined with Seaside, Magritte enable HTML forms and reports generation. The Quuve software (cf. http://www.pharo.org/success) of Debris Publishing company is a brilliant example of Magritte powerfulness: all HTML tables have been automatically generated. Data validation is also defined in Magritte descriptions and not spread in the UI code. This tutorial will not describe this but you can refer to the Seaside book (http://book.seaside.st) and the Magritte tutorial (https://github.com/SquareBracketAssociates/Magritte).
This week, we will start by describing with Magritte the five instance vairables of
TBPost, then we will use these descriptions to automatically generate Seaside components.
The five following methods classified in the 'descriptions' protocol of
TBPost. Note that the name of these methods does not matter although we use naming convention. In fact, the
<magritteDescription> pragma allows Magritte to retrieve descriptions.
A post title is a string and must be filled (required).
The text of a post is a multi-line string that is also mandatory.
The category of a post is an optional string. If it is not specified, the post will belong to the 'Unclassified' category.
The creation date of a post is important to sort posts before displaying them.
visible instance variable must be a boolean value.
We could improve these descriptions and make them more complete. For example, ensure that the date of a new post cannot be before the current date. We could also define the category of post must be one the already existing categories. With richer descriptions, you can produce more complete generated UI elements.
We will now develop the Administration UI of TinyBlog. Through this exercise, we will show how to use session information and Magritte descriptions to define reports. Our objective is: the user should be able to log in using a login and a password to access the administration part of TinyBlog. The link to log in will be placed below the list of categories.
Let's start by developing an authentification Component that will open a modal dialog asking for a login and a password. Note that such a functionnality should be part of a component library of Seaside.
This component illustrates how values can be elegantly retrieved from the user directly the instance variable of the component.
component instance variable is initialized by the following class-side method:
renderContentOn: method define the content of the modal dialog.
What do you think about the above method? Propose a refactoring using multiple methods to better modularize the differents elements of the form.
When the user click on the 'SignIn' button, the
validate message is sent and it verifies the login/password entered by the user to access the 'admin' part.
Authentication should not be the responsability of the modal dialog. It would be better that it delegates this task to another model object that interact the backend to authenticate users. You can look for another method to achieve user authentication (using a database backend, LDAP or simply text files).
TBAuthentificationComponent component could display the name of the currently connected user.
We now integrate a link in the application that will trigger the display of the authentication modal dialog. At the beginning of the
renderContentOn: method of
TBPostsListComponent, we add the render of
TBAuthentificationComponent. We also pass to this component a reference to the component that display the posts.
We define a method that displays a key logo and the 'SignIn' link.
We introduce this link below the list of categories.
Figure 63.1 shows what is displayed when the user click on the 'SignIn' link.
We will create two components. The first one will be a report that contains all posts and the second one will contain this report. The report will be automatically generated with Magritte as a Seaside component and we could have only one component. However, we believe that sperating the administration component from the report is a good pratice regarding for evolution. Let's start by the administration component.
TBAdminComponent inherit from
TBScreenComponent to benefit from the header and access to the blog model. It will contain the report that will create in the following.
We define a first testing version of the
We modify the
validate method to invoke the
gotoAdministration method defined in
TBPostsListComponent. This latter method calls the administration component.
Figure 63.2 illustrates what you obtain after logging in into your application.
The list of posts is displayed by a dynamically generated report with Magritte. We use Magritte here to create the fonctionnalities of the administration part of TinyBlog (list, create, edit and remove posts). For modularity purpose, we create a Seaside component for the report.
We add a class-side method named
from: and pass it the blog object to use to create the report. Since all posts have the same magritte descriptions, we use one post object to retrieve them.
We can now, add a report to the
Since the report is a child component of
TBAdminComponent, we must redefine the
children method as follows.
initialize method of
TBAdminComponent we instanciate a
TBPostsReport and pass it the current blog object to access posts.
We can now display the report in the
By default, the report display all data available in posts even if some columns are not useful. We can filter columns and only display the title, the category and the creation date.
We add a class-side method on
TBPostsReport to select columns and we modify the
from: methode to use it.
Currently, the generated report is raw. There are no titles on columns, columns order is not fixed (it can change from instance to another). We will modify Magritte descriptions of posts to improve this.
Figure 63.3 shows what the report looks like after logging in.
We now set up CRUD (Create Read Update Delete) actions to let administrators manage posts. We will add a enew colum (instance of
MACommandColumn) in the report that will group all operations on posts using
addCommandOn:. This is done during the report creation and we modify the report to have access to the blog.
A link is displayed above the report to add a post (add). Since this link is part of the
TBPostsReport component, we redefine its
renderContentOn: to introduce this
Figure 63.4 shows the new version of the posts report.
Each action (Create/Read/Update/Delete) is associated to one method of the
TBPostsReport objet. We will detail the implementation of each of them that consists in creating a customized form for each action. Indeed, if the user wants to read a post, it does not need a 'save' button that is only needed when editing the post.
renderAddPostForm method demonstrates the power of Magritte to generate forms. In this example, the
asComponent message sent to a model object (instance of
TBPost) directly creates a Seaside component. By adding a decoration to this Seaside component, we can introduce the ok/cancel buttons.
addPost method first displays generated form component returned by
renderAddPostForm: and then add the newly created post to the blog.
Figure 63.5 shows the form to add a post.
To prevent mistakes, we introduce a modal dialog to make the user confirm a post removal. Once removed, the list of posts displayed by the
TBPostsReport component should be refreshed as we will see in the following.
The last method above has been added in the
TBBlog class that belongs to the model of our application. We must write a new unit test to cover this functionnality.
TBPostsReport >> addPost: and
TBPostsReport >> deletePost: correctly modify data in the model (and the database) but the displayed data on screen are not correctly updated. There is a mismatch between data in the model and data displayed by the view. The view (the report) should be refreshed.
Now, the form works well and it also take into account contraints expressed in Magritte descriptions such mandatory fields.
We will now modify Magritte descriptions to make form generators use Bootstrap. First, we specify that the form should be rendered inside a Bootstrap conainer.
We can now, improve the style of the input fields with Bootstrap specific annotations.
Figure 63.6 shows what looks like a form to add a post.
A session object is associated to each instance of Seaside application. A session is dedicated to store informations shared and accessible by all components of the application such as the currently authenticated user. We will describe now how to use a session to manage log in.
The blog admin may want to switch between the private (admin) and public (readers) part of TinyBlog.
We introduce a new subclass of
To know wether a user is connected or not, we define a session object with an instance variable named
logged that contains a boolean value.
We initialize this instance variable to
false when the session is created.
In the admin part of TinyBlog, we add a link to switch to the public part. We use here the
answer message because the administration composant has been called using the
In the public part, we modify the behavior of the application when the user click on the link to access the admin part. This link only opens the authentication modal dialog if the user is not already connected.
TBAuthentificationComponent component shoudl now update the
logged instance variable of the session if the user successfully log in as an administrator.
Finally, we have to configure Seaside to use session object instance of
TBSession for the TinyBlog application.
This is done in the
initialize class-side method of
Before testing, remember that this method must be executed manually
TBApplicationRootComponent initialize, because the class already exists.