Ce TP est le dernier d'une série où vous créez la base d'une application permettant d'afficher et de gérer une liste de signets.
Dans les TP précédents, vous avez vu comment accéder à une API à l'aide de l'API fetch, mettre en place un routage coté client à l'aide de Wouter et gérer des données globales pour l'application à l'aide des contextes React, notamment pour gérer l'utilisateur connecté.
Ce dernier TP, se focalisera sur l'édition de données et la mise en place de formulaires.
Les notes des TP seront obtenues à partir de vos dépôts Git, vous devrez donc prendre grand soin de la qualité de ces dépôts et de leurs « commits ».
De manière similaire, les descriptions de vos « commits » devront être claires et informatives, afin de permettre l'évaluation de la progression de votre travail.
Vous allez maintenant ajouter et regrouper les routes permettant d'interaction avec les signets.
Pour commencer, vous allez créer trois vues pour les signets :
BookmarkCreate pour la création d'un signet, il affichera pour l'instant un message informatif du type « Création de signet »,
BookmarkDelete pour la suppression d'un signet, il affichera pour l'instant un message informatif du type « Suppression du signet {id} » où {id} est l'identifiant du signet à supprimer transmis comme paramètre de route,
BookmarkUpdate pour l'édition d'un signet, il affichera pour l'instant un message informatif du type « Édition du signet {id} » où {id} est l'identifiant du signet à éditer transmis comme paramètre de route.
Comme nous l'avons vu dans le TP précédent pour la vue Bookmark, pour obtenir le paramètre de route id, vous pouvez utiliser la propriété params injecter par le composant Route de wouter.
Vous pourrez ensuite ajouter les routes correspondantes à ces vues au routeur de l'application :
base correspondant au chemin de base des routes, « /bookmarks » par exemple,
Route Wouter :
BookmarkCreate,BookmarkUpdate,BookmarkDelete.L'ordre de déclaration des routes est importante, le routeur affichera la vue de la première route correspondante à la route actuelle.
Vous devriez maintenant pouvoir contrôler que les nouvelles routes sont fonctionnelles en y accédant directement en modifiant directement la route dans votre navigateur.
La navigation entre les différentes routes, en modifiant l'URL de la page, est un peu fastidieuse. Vous allez créer un composant permettant de définir un menu de navigation. Le menu devra être construit spécifiquement pour chaque vue, l'objectif sera donc de simplifier la création de ce menu de navigation.
Vous commencerez par créer un composant NavBtn recevant comme propriétés React (props) :
icon une icône SVG FontAwesome obligatoire à afficher dans le bouton,
to une route optionnelle vers laquelle rediriger l'application, null par défaut.
onClick une fonction de rappel optionnelle, invoquée lors du clic de la souris sur le bouton, null par défaut.
Lorsque la propriété React to est définie, le composant retournera un lien Wouter redirigeant vers la route to, sinon le bouton sera un bouton HTML de type button, chacun contenant l'icône FontAwesome icon.
La définition de la PropType de icon sera un objet JavaScript contenant les propriétés prefix et iconName de type string et isRequired.
Vous pourrez ensuite créer le menu de navigation, sous la forme d'une barre de navigation avec deux zones de boutons optionnelles, à gauche et à droite, comme illustré dans la maquette suivante :
Vous définirez donc un composant Nav recevant comme propriétés React (props) left et right deux tableaux optionnels (vides par défaut). Ces tableaux contiendront les composants NavBtn à afficher.
Un exemple de construction de menu pourrait être le suivant :
Vous pourrez ajouter le balisage nécessaire pour obtenir le rendu souhaité.
NavBtn.
Nav.
Vous allez maintenant mettre en place la navigation entre les différentes routes de l'application dans chaque vue.
Bookmarks vous ajouterez un bouton à droite vers la création d'un signet.
BookmarkCreate vous ajouterez un bouton à gauche permettant de revenir vers la liste des signets.
Bookmark vous ajouterez un bouton à gauche permettant de revenir vers la liste des signets et deux boutons à droite vers la modification et la suppression du signet.
BookmarkDelete vous ajouterez un bouton à gauche permettant de revenir vers le détail du signet.
BookmarksUpdate vous ajouterez un bouton à gauche permettant de revenir vers le détail du signet.
Pour le bouton permettant de revenir vers la liste des signets, vous n'utiliserez pas la navigation du routeur. Vous préférez utiliser la méthode back de la classe History du navigateur, qui permettra de simuler un clic sur le bouton retour du navigateur. Ainsi, la pagination stockée sous forme de paramètre d'URL sera utilisé pour rétablir l'interface précédente.
Tous les autres boutons utiliseront la navigation du routeur, pour provoquer la navigation dans l'application.
Vous devriez maintenant pouvoir contrôler que les routes sont toujours fonctionnelles en passant par l'interface.
Vous allez maintenant permettre à l'utilisateur de créer un nouveau signet.
Pour commencer, vous créerez un nouveau composant BookmarkForm qui affichera un formulaire HTML permettant de saisir les données d'un signet.
Le composant recevra comme propriété React (props) :
title le titre du formulaire,
submit le texte du bouton de soumission,
onSubmit la fonction à invoquer lors de la soumission du formulaire.
Les informations minimales nécessaires à l'API pour créer un signet sont un nom, une URL, une description et un attribut rendant le signet public ou privé.
React propose deux approches pour gérer les formulaires :
La documentation de la seconde approche vous montre une solution reposant sur la propriété target de l'événement onSubmit. C'est cette solution que vous allez employer en ajoutant un gestionnaire d'événements sur la soumission du formulaire, vous permettant d'accéder aux éléments du formulaire grâce à l'événement transmis en paramètre. Par souci d'optimisation des rendus de l'application, vous utiliserez le hook de React useCallback pour mémoïser la fonction de rappel de soumission du formulaire.
Dans cette fonction mémoïsée, vous pourrez invoquer la fonction onSubmit en lui transmettant en paramètre les données du signet à créer, issues du formulaire. Vous veillerez à prévenir le comportement par défaut de l'événement pour empêcher le rechargement de la page.
Pour finir, vous utiliserez ce composant dans la vue de la création d'un signet en transmettant la fonction console.log comme props onSubmit. Vous devriez maintenant pouvoir tester votre formulaire et obtenir l'affichage des données dans la console de développement.
BookmarkForm.
BookmarkForm dans la vue BookmarkCreate.
Pour finaliser la création du signet, vous allez réaliser la requête de création du signet dans l'API lors de la soumission du formulaire.
Vous commencerez par ajouter un nouveau service d'interaction avec l'API sous la forme d'une fonction postBookmark(data) au script « src/services/api/bookmarks.js », permettant la création d'un signet.
Cette interaction doit se faire en étant authentifié, vous penserez donc bien à transmettre votre cookie de session lors de vos requêtes. Vous pourrez utiliser la méthode de classe JSON.stringify() pour générer le contenu de la requête. Et le type mime de la requête pour le contenu transmis à l'API doit être : « application/ld+json ».
Pour finir, dans la vue BookmarkCreate, vous ferez en sorte que la soumission du formulaire :
useLocation de Wouter.
Lorsque le signet créé est privé, il n'est pas accessible aux autres utilisateurs et vous ne pouvez voir vos signets privés que si vous êtes authentifié. Vous veillerez donc à contrôler que vos requêtes fetchAllBookmarks et getBookmarkDetail fournissent bien votre cookie de session afin d'obtenir les signets privés aussi.
postBookmark(data) au script src/services/api/bookmarks.js.
postBookmark(data) dans la vue BookmarkCreate.
Sur le même principe, pour éditer un signet, vous ajouterez une fonction patchBookmark(bookmarkId, data) au script src/services/api/bookmarks.js, permettant l'édition d'un signet.
Le composant BookmarkForm que vous avez créé précédemment, pour la création d'un signet, pourra être réutilisé pour l'édition d'un signet. Vous lui ajouterez une nouvelle propriété React (props) optionnel bookmarkData pour lui transmettre les données du signet à éditer. Et vous utiliserez ces données pour initialiser les champs du formulaire lorsqu'elles sont présentes.
Dans la vue BookmarkUpdate, de manière similaire à la vue BookmarkDetail, vous réaliserez une requête permettant d'obtenir les données du signet à éditer. Vous utiliserez ces données pour initialiser le formulaire de modification du signet.
Enfin, pour finir, lors de la soumission du formulaire, vous ferez en sorte que le composant :
Vous pourrez contrôller que tout fonctionne correctement, notamment que la vue de la création d'un composant est toujours fonctionnel.
patchBookmark(bookmarkId, data) au script src/services/api/bookmarks.js.
BookmarkForm.
BookmarkForm dans la vue BookmarkUpdate.
patchBookmark(bookmarkId, data) dans la vue BookmarkUpdate.
Pour finir les interactions avec l'API, vous allez ajouter la suppression d'un signet. Vous commencerez par ajouter une fonction deleteBookmark(bookmarkId) au script src/services/api/bookmarks.js, permettant la suppression d'un signet dans l'API.
Vous ajouterez ensuite à la vue BookmarkDelete un message demandant la confirmation pour la suppression du signet, ainsi que deux boutons ; pour annuler ou confirmer la suppression.
Le bouton d'annulation provoque le retour à la vue précédente, le détail du signet.
Le bouton de confirmation :
deleteBookmark(bookmarkId) au script src/services/api/bookmarks.js.
BookmarkDelete.
deleteBookmark(bookmarkId) dans la vue BookmarkDelete, lors de la validation de la suppression.
Vous avez dû constater que les interactions avec l'API peuvent produire des erreurs. Des exemples faciles peuvent être la modification d'un signet ne vous appartenant pas ou la modification d'un signet vous appartenant, mais avec des valeurs non-valides. Vous allez donc voir comment gérer ces erreurs.
Pour commencer, vous allez détecter et informer l'application de ces erreurs lors des requêtes postBookmark, patchBookmark et deleteBookmark. Vous ajouterez la fonction suivante au script src/services/api/bookmarks.js :
Cette fonction reçoit en paramètre une promesse de réponse de l'API fetch et retourne cette même réponse. Mais dans le cas ou la réponse n'a pas été fructueuse, elle ajoute une promesse de jet d'exception contenant l'erreur produite en JSON par l'API.
Vous utiliserez en suite cette fonction lors des requêtes postBookmark, patchBookmark et deleteBookmark.
Pour gérer les messages d'erreur, afin de mutualiser le code, vous créerez un nouveau hook personnalisé useApiError. Ce hook ne recevra pas de paramètre, définira une variable d'état errors contenant les erreurs à afficher sous la forme d'un tableau de chaîne de caractères et retournera un objet JavaScript contenant deux propriétés :
errors, contenant les erreurs à afficher,
setApiError, une fonction permettant de remplir la liste des erreurs à afficher.
Vous utiliserez le hook useCallback pour créer la fonction setApiError. Cette fonction recevra en paramètre optionnel l'erreur de l'API et en conséquence remplira la liste des erreurs à afficher :
violations de l'erreur),
detail de l'erreur).Pour afficher les erreurs, vous créerez un nouveau composant Errors qui recevra en paramètre un tableau de chaîne de caractères des erreurs à afficher. Son rôle sera de réaliser l'affichage de ces erreurs.
Dans les vues BookmarkUpdate, BookmarkCreate et BookmarkDelete, vous utiliserez les exceptions des requêtes vers l'API pour obtenir les erreurs et le hook personnalisé useApiError et le composant Errors pour en informer l'utilisateur.
useApiError.Errors.BookmarkUpdate, BookmarkCreate et BookmarkDelete.L'application est fonctionnelle et la sécurité des données est assurée par l'API. Cependant, l'expérience utilisateur n'est pas optimale sans gestion des rôles des utilisateurs. Par exemple, un utilisateur anonyme ne devrait pas avoir accès à la zone de création d'un signet. Ni un utilisateur accès à la modification d'un signet ne lui appartenant pas.
Pour commencer, vous allez cacher les boutons n'ayant pas sens dans l'interface. Vous commencerez par créer un nouveau hook personnalisé usePermissions qui retournera un object JavaScript comportant 2 propriétés :
isAuthenticated, contenant un booléen définissant si un utilisateur est définie dans le context de l'utilisateur courant,
isUser, contenant une fonction recevant l'IRI d'un propriétaire de ressource de l'API et qui retournera un booléen définissant si cet IRI correspond à l'utilisateur courant.
Pour contrôller que l'utilisateur en paramètre identifie l'utilisateur courant, vous pourrez utiliser le service de transformation iriToId pour le comparer à l'identifiant de l'utilisateur du context. Il est cependant possible que vous deviez modifier le service pour qu'il retourne une valeur entière.
Vous pourrez ensuite utiliser ce hook personnalisé pour ajouter de manière conditionnelle les boutons de navigation de création, suppression et édition.
Maintenant votre interface est plus cohérent, mais même si les routes ne sont pas accessibles par l'interface, elles sont tout de même disponibles par l'URL, ce qui peut amener à des comportements contre-intuitifs de l'application, notamment lors de la déconnexion de l'utilisateur. Vous allez donc réaliser, dans les vues suivantes, une redirection vers la page précédente :
BookmarkCreate lorsque l'utilisateur n'est pas authentifié,BookmarkUpdate et BookmarkDelete lorsque l'utilisateur n'est pas le propriétaire.usePermissions.