playlist Dynamisation avec XHR

Publié il y a 6 mois dans la série : Rails
Nicolas Cavigneaux
Votre formateur
Nicolas Cavigneaux

A la recherche d'un langage polyvalent, j'ai fait la découverte de Ruby en 2003. J'ai donc très vite commencé à utiliser Ruby au quotidien pour des tâches diverses et variées (scripting, applications lourdes…).

Courant 2004, une vague de fraicheur est apparue avec l'arrivée de Ruby on Rails qui m'a de suite conquis. J'ai donc décidé de participer activement à la communauté (forums, patches, librairies, …). En 2010, je fais la rencontre de Martin Catty et retrouve dans sa vision la rigueur et les bonnes pratiques que j'aime mettre en place, le déclic a donc été immédiat.

Synbioz met en place des solutions robustes sur la base d'outils modernes et funs, je veux faire partie de l'aventure.

Catégories : Développement

Dans cette épisode nous allons voir comment rendre l'expérience utilisateur plus agréable grâce à l'utilisation d'XHR et de la mise à jour en temps réel du contenu grâce à javascript
Afficher le transcript complet de la vidéo
Bonjour et bienvenue dans cette vidéo consacrée à Ruby on Rails.


Aujourd'hui nous allons dynamiser un peu notre application en ajoutant un peu d'AJAX. AJAX est un acronyme utilisé un peu librement aujourd'hui mais pour résumer c'est le fait de faire communiquer du javascript avec le serveur de manière asynchrone puis d'éventuellement modifier le DOM existant lors d'un retour du serveur.


Pour illustrer ce principe nous allons une fois de plus nous appuyer sur notre panier. Plutôt que d'avoir uniquement une page séparée pour afficher le panier, on va l'afficher en permanence dans le layout.


Pour arriver à nos fins on va utiliser le concept de partiel qui permet de gérer un morceau de vue de manière autonome pour pouvoir la ré-utiliser à plusieurs endroits.


Extraction dans un partiel


Éditons le fichier app/views/carts/show.html.erb pour en extraire la partie qui nous intéresse. Pour faire simple, on souhaite tout reprendre.


Nous allons copier le contenu dans le fichier app/views/carts/_cart.html.erb. Pourquoi ce fichier ? Tout d'abord, par convention les fichiers de partiel sont censés commencer par un underscore. Aussi lorsqu'on rend un partiel en lui passant un objet, Rails va faire le lien automatiquement entre son type et le nom du partiel attendu.


Dans notre cas, nous allons passer une instance de Cart, rails va donc chercher un fichier _cart. Allons-y :


On fait une copie de app/views/carts/show.html.erb vers app/views/carts/_cart.html.erb.


Et on remplace la variable d'instance @cart par une variable locale. En effet, quand on utilise render en lui passant un objet, une variable du même nom que le partiel sera mise automatiquement à disposition et contiendra les données de l'objet passé en paramètre à render.


On a également supprimé le bouton de retour à la liste des produits qui devient superflu dans le layout.


On remplace ensuite le code de la vue show par l'appel au partiel qu'on vient de créer :

<%= render @cart %>


On peut aller vérifier que note panier s'afficher toujours correctement.


Maintenant ajoutons l'affichage de ce panier au layout général :

<h1 class="display-1 p-3 bg-secondary text-white rounded">Best Shop</h1>
<div class="row">
  <div class="col-4"><%= render @cart %></div>
  <div class="col-8">
    <p id="notice"><%= notice %></p>
    <p id="alert"><%= alert %></p>

    <%= yield %>
  </div>
</div>


On modifie également la taille des encarts produit en remplaçant la classe w-50 par w-100 dans le fichier app/views/catalog/index.html.erb pour exploiter tout l'espace disponible.


Et n'oublions pas de charger systématiquement le panier dans application_controller pour qu'il soit disponible dans le layout :

include CurrentCart
before_action :set_cart


On peut maintenant vérifier le rendu sur la page d'accueil.


Dynamisation


Maintenant que notre panier est toujours visible, nous n'avons plus besoin de rediriger sur la page panier quand un produit est ajouté, il nous suffit maintenant de recharger la page courante.


Pour ça, une seule petite modification est nécessaire dans le contrôleur line_items_controller.rb :

format.html { redirect_to root_url }


On peut tester notre modification en ajoutant un produit au panier et effectivement la page est rechargée et la mise à jour prise en compte.


Jouons nos tests. Sans grande surprise plusieurs de nos tests sont cassés. Si on lit attentivement le message d'erreur on comprend que dans certains cas, le layout essaie de rendre le partiel cart alors qu'il n'y a aucun panier disponible.


Corrigeons ce point en modifiant le layout :

<% if @cart && @cart.line_items.any? %>
  <%= render @cart %>
<% else %>
  <h2>Panier vide</h2>
<% end %>


Maintenant on vérifie qu'un panier est disponible avant d'essayer de l'afficher. S'il n'y en a pas, on affiche un contenu alternatif.


On peut relancer nos tests… Et c'est déjà mieux. Le dernier test cassé restant est on ne peut plus logique. On a modifié la redirection effectuée après l'ajout d'un produit de la page panier à la page d'accueil. Les tests s'en plaignent, corrigeons ça :

assert_redirected_to root_url


On rejoue nos tests et cette fois c'est tout bon, on peut continuer les améliorations !


Rafraîchissement JS


Pour que l'expérience utilisateur soit encore plus satisfaisante, on va tirer partie des possibilités que nous offre Javascript pour soumettre l'ajout de produit dynamiquement au serveur et mettre à jour le panier sans même recharger la page, simplement grâce à Javascript.


Comme souvent, Rails nous fournit l'outillage nécessaire à mettre en place ces fonctionnalités évoluées de manière très simple.


Il nous suffit d'ajouter une option au bouton d'ajout au panier pour signaler à Rails que nous voulons faire la soumission en AJAX, allons y :

<%= button_to 'Ajouter au panier', line_items_path(product_id: product), class: "btn btn-primary", remote: true %>


Avec cet unique changement, la soumission du formulaire ne se fera plus classiquement mais à travers un appel XHR en JS en arrière plan. Ça suffit techniquement à ce que l'ajout soit pris en compte mais il faudrait qu'on s'assure de mettre à jour le panier à la volée lorsque le serveur nous répond.


La première chose à faire est d'indiquer à notre action qu'on veut réagir de manière spécifique lorsque celle ci est appelée en AJAX.


Si on retourne dans l'action create du contrôleur line_items_controller.rb on voit qu'il y a un bloc respond_to qui définie le comportement en fonction du format de la requête, pour l'instant nous avons un comportement pour le format HTML et un comportement pour le format JSON.


On va simplement ajouter un bloc en plus pour supporter le format Javascript :

format.js


Le simple ajout de cette ligne fait que si l'action est appelée au format Javascript, Rails va chercher à rendre un template create.js.erb. Le JS qu'on va y mettre sera interprété par le navigateur lorsque le serveur aura rendu la main.


Sachant ça il ne nous reste plus qu'à faire en sorte que le JS rendu remplace le contenu du panier par la nouvelle version. Créons donc ce fichier et ajoutons y le JS qui va faire le travail :

document.getElementById("cart").innerHTML = "<%=j render(@cart) %>"


Dans les fichier js.erb on peut écrire du JS classique mais en plus avoir une sur-couche ERB qui sera interprété avant de retourner le JS au navigateur.


Dans notre cas, on cherche un élément ayant cart pour id. On va ensuite remplacer le contenu HTML de cet élément par le rendu de notre partiel cart comme on le faisait dans les vues HTML classiques. Seule petite différence, l'appel à l'helper j qui échappe le Javascript pour éviter tout souci à l'interprétation.


On aurait difficilement faire plus facile. Il nous reste tout de même à ajouter l'id en question au conteneur de notre panier, allons y :

<div class="col-4" id="cart">


On recharge la page et on test l'ajout d'un produit. Et ça fonctionne, on a maintenant le panier qui se met à jour sans que la page soit rechargée. Si on regardait dans la console du navigateur on verrait qu'on a bien un appel XHR qui nous retourne notre morceau de JS avec le DOM pour le panier à jour.


On pourrait sûrement ajouter un effet visuel en CSS quand le contenu change mais cette amélioration sort du cadre de cette série sur Rails.


Pour finir il serait intéressant de faire en sorte que le bouton de suppression fonctionne aussi en XHR. On ajoute donc un remote: true dessus :

<%= button_to "Vider le panier", @cart, method: :delete, data: {
confirm: "Êtes-vous sûr ?" }, class: "btn btn-danger", remote: true %>


On se contente de ça, aucune modification supplémentaire au niveau du contrôleur. Rails est fourni avec une librairie Javascript appelée Turbolinks qui prendra la main si aucun template JS custom n'est fourni. Le fonctionnement de turbolink est simple, faire une appel classique à l'application Rails, récupérer le contenu HTML et remplacer tout le body à la volée, sans toucher à la partie HEAD. C'est plus radical que notre solution ciblée mais ça a tout de même le mérite d'être plus réactif que de rendre une page complète dans le navigateur, recharger les stylesheets et js présents dans le header, etc.


Voilà pour cette partie sur la dynamisation grâce aux requêtes XHR qui nous permettent de rendre notre application plus réactive et de fait plus agréable à utiliser.


Dans la prochaine vidéo, nous mettrons en place le système de validation de la commande.


À bientôt.

8/9 dans la sérieRails