Vous pouvez charger la correction de la semaine précédente en exécutant le code suivant:
Assurez vous que votre serveur Teapot est bien stoppé.
Le travail présenté dans la suite est indépendant de celui sur Voyage et sur la base de données MongoDB.
Nous commençons par définir une interface telle que les utilisateurs la verrons. Dans un prochain chapitre nous développerons une interface d'administration que le possesseur du blog utilisera. Nous allons définir des composants Seaside http://www.seaside.st dont l'ouvrage de référence est disponible en ligne à http://book.seaside.st.
Il existe deux façons pour démarrer Seaside. La première consiste à exécuter le code suivant :
La deuxième façon est graphique via l'outil Seaside Control Panel (World Menu>Tools>Seaside Control Panel).
Dans le menu contextuel de cet outil (clic droit), cliquez sur "add adaptor..." pour ajoutez un serveur ZnZincServerAdaptor
, puis définissez le port (e.g. 8080) sur lequel le serveur doit fonctionner (comme illustré dans la figure 55.1). En ouvrant un navigateur web à l'URL http://localhost:8080, vous devez voir s'afficher la page d'accueil de Seaside comme sur la figure 55.2.
Créez la classe TBApplicationRootComponent
qui est le point d'entrée de l'application. Elle sert à l'initialisation de l'application.
Nous déclarons l'application au serveur Seaside, en définissant coté classe, dans le protocole 'initialization'
la méthode initialize
suivante. On en profite pour intégrer les dépendances du framework Bootstrap (les fichiers css et js seront stockés dans l'application).
Exécuter TBApplicationRootComponent initialize
pour forcer l'exécution de la méthode initialize
. En effet les méthodes initialize
de classe ne sont automatiquement exécutées que l'on du chargement de la classe. Ici nous venons juste de la définir et donc il est nécessaire de l'exécuter pour en voir les bénéfices.
Les méthodes de classe initialize
sont invoquées automatiquement lors du chargement de la classe.
Ajoutons également la méthode canBeRoot
afin de préciser que la classe TBApplicationRootComponent
n'est pas qu'un simple composant Seaside mais qu'elle représente notre application Web.
Elle sera donc instanciée dès qu'un utilisateur se connecte sur l'application.
Une connexion sur le serveur Seaside ("Browse the applications installed in your image") permet de vérifier que l'application TinyBlog est bien enregistrée comme le montre la figure 55.3.
Ajoutons maintenant une méthode d'instance renderContentOn:
dans le protocole rendering
afin de vérifier que notre application répond bien.
En se connectant avec un navigateur sur http://localhost:8080/TinyBlog, la page affichée doit être similaire à celle sur la figure 55.4.
Ajoutons maintenant des informations dans l'entête de la page HTML afin que TinyBlog ait un titre et soit une application HTML5.
Le composant TBApplicationRootComponent
est le composant principal de l'application, il ne fait que du rendu graphique limité.
Dans le futur, il contiendra des composants et les affichera.
Par exemple, les composants principaux de l'application permettant l'affichage des posts pour les lecteurs du blog mais également des composants pour administrer le blog et ses posts.
Pour cela, nous avons décidé que le composant TBApplicationRootComponent
contiendra des composants héritant tous de la classe abstraite TBScreenComponent
que nous allons définir dans le prochain chapitre.
Nous sommes maintenant prêts à définir les composants visuels de notre application Web. Les premiers chapitres de http://book.seaside.st peuvent vous y aider et compléter efficacement ce tutoriel.
La figure 55.5 montre les différents composants que nous allons développer et ou ils se situent.
Le composant TBApplicationRootComponent
contiendra des composants sous-classes de la classe abstraite TBScreenComponent
. Cette classe nous permet de factoriser les comportements que nous souhaitons partager entre tous nos composants.
Les différents composants d'interface de TinyBlog auront besoin d'accéder aux règles métier de l'application. Dans le protocole 'accessing', créons une méthode blog
qui retourne une instance de TBBlog
(ici notre singleton).
En inspectant l'objet blog retourné par TBBlog current
, vérifier qu'il contient bien des posts. Si ce n'est pas le cas, exécuter createDemoPost
.
Dans le futur, lorsque nous gérerons les utilisateurs et le fait qu'un utilisateurs puisse avoir plusieurs blogs nous modifierons cette méthode pour utiliser des informations stockées dans la session active (Voir TBSession
plus loin).
La bibliothèque Bootstrap est totalement accessible depuis Seaside comme nous allons le montrer. Pour parcourir les nombreux exemples, cliquer sur le lien bootstrap dans la liste des applications servies par Seaside ou pointer votre navigateur sur le lien http://localhost:8080/bootstrap. Vous devez obtenir l'écran de la figure 55.6.
Cliquer sur le lien Examples au bas de la page et vous pouvez ainsi voir les éléments graphiques ainsi que le code pour les obtenir comme montré par la figure 55.7.
Le repository pour le source et la documentation est http://smalltalkhub.com/#!/~TorstenBergmann/Bootstrap. Une démo en ligne est disponible à l'adresse : http://pharo.pharocloud.com/bootstrap. Cette bibliothèque a déjà été chargée dans l'image PharoWeb utilisée dans ce tutoriel.
Profitons de ce composant pour insérer dans la partie supérieure de chaque composant, l'instance d'un composant représentant l'entête de l'application.
Le protocole 'rendering' contient la méthode renderContentOn:
chargée d'afficher l'entête.
L'entête (header) est affichée à l'aide d'une barre de navigation Bootstrap (voir la figure 55.8)
Par défaut dans une barre de navigation Bootstrap, il y a un lien sur tbsNavbarBrand qui est ici inutile (sur le titre de l'application). Ici nous l'initialisons avec une ancre '#'
de façon à ce que si l'utilisateur clique sur le titre, il ne se passe rien. En général, cliquer sur le titre de l'application permet de revenir à la page de départ du site.
Le nom du blog devrait être paramétrable à l'aide d'une variable d'instance dans la classe TBBlog
et le header pourrait afficher ce titre.
Il n'est pas souhaitable d'instancier systématiquement le composant à chaque fois qu'un composant est appelé. Créons une variable d'instance header
dans TBScreenComponent
que nous initialisons.
Créons une méthode initialize
dans le protocole 'initialize-release':
En Seaside, les sous-composants d'un composant doivent être retournés par le composite en réponse au message children
. Définissons que l'instance du composant TBHeaderComponent
est un enfant de TBScreenComponent
dans la hiérarchie des composants Seaside (et non entre classes Pharo). Nous faisons cela en spécialisant la méthode children
.
Affichons maintenant le composant dans la méthode renderContentOn:
(protocole 'rendering'):
Bien que le composant TBScreenComponent
n'ait pas vocation à être utilisé directement, nous allons l'utiliser de manière temporaire pendant que nous développons les autres composants.
Nous ajoutons main
comme variable d'instance dans la classe TBApplicationRootComponent
. Nous l'initialisons dans la méthode initialize
suivante.
Nous déclarons aussi la relation de contenu en retournant main parmi les enfants de TBApplicationRootComponent
.
Si vous faites un rafraichissement de l'application dans votre navigateur web vous devez voir la Figure 55.8.
Le nom du blog doit être particularisable en utilisant par exemple une variable d'instance de la classe TBBlog
et l'entête (header) pour afficher ce titre.
Nous allons afficher la liste des posts - ce qui reste d'ailleurs le but d'un blog. Ici nous parlons de l'accès public offert aux lecteurs du blog. Dans le futur, nous proposerons une interface d'administration des posts.
Créons un composant TBPostsListComponent
qui hérite de TBScreenComponent
:
Ajoutons une méthode renderContentOn:
(protocole rendering) provisoire pour tester l'avancement de notre application (voir Figure 55.9).
Nous pouvons maintenant dire au composant de l'application d'utiliser ce composant. Pour cela nous modifions-la ainsi:
Editer cette méthode n'est pas une bonne pratique. Nous ajoutons une méthode setter qui nous permettra de changer dynamiquement de composant dans le futur tout en gardant le composant actuel pour une initialisation par défaut.
Nous allons maintenant définir le composant TBPostComponent
qui affiche le contenu d'un post.
Chaque post du blog sera représenté visuellement par une instance de TBPostComponent
qui affiche le titre, la date et le contenu d'un post.
Ajoutons la méthode renderContentOn:
qui définit l'affichage du post.
Dans le chapitre et celui sur l'interface d'administration et qui utilise Magritte , nous montrerons qu'il est rare de définir un composant de manière aussi manuelle. En effet, Magritte permet de décrire les données manipulées et offre ensuite la possibilité de générer automatiquement des composants Seaside. Le code équivalent serait comme suit:
Maintenant nous pouvons afficher des posts présents dans la base.
Il ne reste plus qu'à modifier la méthode TBPostsListComponent >> renderContentOn:
pour afficher l'ensemble des blogs visibles présents dans la base.
Rafraichissez la page de votre navigateur et vous devez obtenir l'erreur suivante:
Not Found /TinyBlog
Par défaut, lorsqu'une erreur se produit dans une application, Seaside retourne une page HTML contenant un message d'erreur générique. Vous pouvez changer ce message mais le plus pratique pendant le développement de l'application est de configurer Seaside pour qu'il ouvre un debugger dans Pharo. Pour cela, exécuter le code suivant :
Rafraichissez la page de votre navigateur et vous devez obtenir un debugger côté Pharo. L'analyse de la pile d'appels montre qu'il manque la méthode suivante :
Rafraichissez à nouveau la page de votre navigateur et vous devez obtenir la Figure 55.10.
Nous allons utiliser Bootstrap pour rendre la liste un peu plus jolie en utilisant un container.
Rafraichissez la page et vous devez obtenir la Figure 55.11.
Les posts sont classés par catégorie. Par défaut, si aucune catégorie n'a été précisée, ils sont rangés dans une catégorie spéciale dénommée "Unclassified".
Nous allons créer un composant pour gérer une liste de catégories nommée: TBCategoriesComponent
.
Nous avons besoin d'un composant qui affiche la liste des catégories présentes dans la base et permet d'en sélectionner une. Ce composant devra donc avoir la possibilité de communiquer avec le composant TBPostsListComponent
afin de lui communiquer la catégorie courante.
Nous définissons aussi une méthode de création au niveau classe.
La méthode selectCategory:
(protocole 'action') communique au composant TBPostsListComponent
la nouvelle catégorie courante.
Nous avons donc besoin d'ajouter une variable d'instance pour stocker la catégorie courante dans TBPostsListComponent
.
Nous pouvons maintenant ajouter une méthode (protocole 'rendering') pour afficher les catégories sur la page. En particulier pour chaque catégorie nous définissons le fait que cliquer sur la catégorie la sélectionne comme la catégorie courante.
Reste maintenant à écrire la méthode de rendu du composant: On itère sur toutes les catégories et on les affiche
Nous avons presque fini mais il faut encore afficher la liste des catégories et mettre à jour la liste des posts en fonction de la catégorie courante.
Pour cela, modifions la méthode de rendu du composant TBPostsListComponent
.
La méthode readSelectedPosts
récupère dans la base les posts à afficher. Si elle vaut nil
, l'utilisateur n'a pas encore sélectionné une catégorie et l'ensemble des posts visibles de la base est affiché. Si elle contient une valeur autre que nil
, l'utilisateur a sélectionné une catégorie et l'application affiche alors la liste des posts attachés à la catégorie.
Nous pouvons maintenant modifier la méthode chargée du rendu de la liste des posts:
Une instance du composant TBCategoriesComponent
est ajoutée sur la page et permet de sélectionner la catégorie courante (voir la figure 55.12).
Nous allons maintenant agencer le composant TBPostsListComponent
en utilisant une mise en place d'un 'responsive design' pour la liste des posts. Cela veut dire que le style CSS va adapter les composants à l'espace disponible.
Les composants sont placés dans un container Bootstrap puis agencés sur une ligne avec deux colonnes. La dimension des colonnes est déterminée en fonction de la résolution (viewport) du terminal utilisé. Les 12 colonnes de Bootstrap sont réparties entre la liste des catégories et la liste des posts. Dans le cas d'une résolution faible, la liste des catégories est placée au dessus de la liste des posts (chaque élément occupant 100% de la largeur du container).
Vous devez obtenir une application proche de celle représentée figure 55.13.
Lorsqu'on sélectionne une catégorie, la liste des posts est bien mise à jour. Toutefois, l'entrée courante dans la liste des catégories n'est pas sélectionnée. Pour cela, on modifie la méthode suivante :
Bien que le code fonctionne, on ne doit pas laisser la méthode TBPostsListComponent >> renderContentOn: html
dans un tel état. Elle est bien trop longue et difficilement réutilisable.
Proposer une solution.
Nous voici prêts à définir la partie administrative de l'application.
A titre d'exercice, vous pouvez :
L'application finale devrait ressembler à la figure 55.14.