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.
The 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.
The component
instance variable is initialized by the following class-side method:
The 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).
Moreover, the 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 renderContentOn:
method:
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 TBAdminComponent
.
Since the report is a child component of TBAdminComponent
, we must redefine the children
method as follows.
In the 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 renderContentOn:
method.
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 add
link.
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.
The 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.
The 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.
Methods 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 WASession
named TBSession
.
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 call:
message.
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.
The 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 TBApplicationRootComponent
.
Before testing, remember that this method must be executed manually TBApplicationRootComponent initialize
, because the class already exists.