Vue.js : Les extensions

La librairie la mieux dotée nécessite forcément des extensions et Vue.js n’échappe pas à la règle. On a déjà vu qu’il est possible de créer des composants. Vue.js permet l’écriture de plugins et il commence à y en avoir quelques uns de disponibles. Dans ce chapitre on va utiliser le plugin vue-resource qui permet de faciliter la communication avec un serveur.

Un plugin ?

Un plugin (on dit aussi un greffon parfois pour rester français) est une source complémentaire qui étend les fonctionnalités globales d’une librairie. La nécessité d’utiliser un plugin pour vue.js peut se détailler en :

  • la nécessité de disposer de méthodes globales,

  • la nécessité de disposer de filtres, directives…

  • la nécessité de disposer de nouvelles méthodes pour les VueModèles.

Le but du présent chapitre n’est pas de créer un plugin mais juste d’en utiliser un.

Le plugin vue-resource

On trouve ce plugin sur Github :

img01

Ce qui va essentiellement nous intéresser ce sont ces fichiers Javascript :

img02

De façon classique on dispose d’une version lisible et commentée et d’une autre version minifiée pour alléger le chargement. Si vous voulez effectuer du débogage dans le code utilisez la première version, sinon adoptez directement la seconde.

Il suffit de télécharger le fichier, de le disposer dans un dossier, et de le référencer correctement dans la page HTML, par exemple :

<script src="js/vue-resource.min.js"></script>

On peut également utiliser des outils pour rassembler les ressources dans un fichier unique si vous voulez optimiser le chargement.

Une autre possibilité est d’utiliser un CDN :

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-resource/0.7.0/vue-resource.min.js"></script>

Ce plugin offre deux services :

1 . un simple service HTTP avec ces méthodes :

  • get(url, [data], [success], [options])

  • post(url, [data], [success], [options])

  • put(url, [data], [success], [options])

  • patch(url, [data], [success], [options])

  • delete(url, [data], [success], [options])

  • jsonp(url, [data], [success], [options])

2 . un service de ressource avec une seule méthode pour créer toutes les possibilités :

  • resource(url, [params], [actions], [options])

Ce qui a pour effet de générer ces méthodes :

  • get: {method: ‘GET’}

  • save: {method: ‘POST’}

  • query: {method: ‘GET’}

  • update: {method: ‘PUT’}

  • remove: {method: ‘DELETE’}

  • delete: {method: ‘DELETE’}

On a donc tout ce qui est nécessaire pour dialoguer facilement avec un serveur. Dans ce chapitre on va utiliser le second service.

Une API

Un serveur de développement

Pour nos tests on a besoin d’un serveur qui réponde aux requêtes en offrant les services d’une API.

Nos besoins vont être une API répondant à l’url « /noms » avec ces 3 routes :

  • GET pour récupérer tous les noms

  • POST pour ajouter un nom

  • DELETE pour supprimer un nom avec l’identifiant en paramètre

Pour ne pas trop charger le code je ne prévois pas la modification du nom. Le but de cet article n’étant pas consacré aux scripts côté serveur on va se contenter de mettre en place quelque chose de simple. Si vous savez déjà comment réaliser cela, par exemple en utilisant Lumen, vous pouvez sauter cette partie (arrangez-vous toutefois pour adapter les données transmises au code Javascript utilisé).

Il vous faut un serveur local genre WAMP ou autre. PHP dispose aussi maintenant en interne d’un serveur simplifié parfaitement adapté au développement.

Créez un dossier sur votre serveur pour les tests pour ce cours. Par exemple j’ai personnellement créé www/vuejs/serveur pour créer les codes de ce chapitre.

Composer

Composer est un gestionnaire de dépendances pour PHP. On lui dit quels composants on désire et lui se fait un plaisir de les installer pour nous. En plus il met en place un chargement automatique des classes concernées. Et pour clôturer la chose il assure aussi les mises à jours !

Si vous ne disposez pas de Composer sur votre machine c’est le moment de l’installer. Le plus simple est de le placer globalement, référez-vous à ces instructions d’installation qui dépendent de votre système d’exploitation. Si vous ne désirez pas cette installation globale vous pouvez tout simplement utiliser le fichier phar. Il suffit de mettre ce fichier dans votre dossier sur le serveur !

On crée notre application côté serveur

Vous avez à présent tout ce qu’il faut pour créer l’application côté serveur. Ouvrez votre console et positionnez-vous dans le dossier que vous avez créé sur le serveur et entrez cette commande pour installer un routeur :

composer require league/route

Si vous utilisez le fichier phar il faudra adapter la syntaxe :

php composer.phar require league/route

Vous devriez obtenir quelque chose dans ce genre :

img03

Vous allez trouver plein de chose dans votre dossier :

img04

Je ne rentre pas dans le détail de tout ça mais maintenant on dispose d’un routeur simple et rapide largement suffisant pour nos tests.

Il faut aussi ajouter un fichier .htaccess pour simplifier nos URL :

<IfModule mod_rewrite.c>
    <IfModule mod_negotiation.c>
        Options -MultiViews
    </IfModule>

    RewriteEngine On

    # Redirect Trailing Slashes...
    RewriteRule ^(.*)/$ /$1 [L,R=301]

    # Handle Front Controller...
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ index.php [L]
</IfModule>

Comme on va utiliser un fichier index.php pour récupérer toutes les url on explique à Apache qu’il doit tout envoyer sur lui. On va d’ailleurs créer ce fichier :

<?php
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use League\Route\RouteCollection;
 
// Autoload
require 'vendor/autoload.php';
 
// Création du routeur
$router = new RouteCollection;
 
// Création routes
$router->addRoute('GET', '/noms', function (Request $request, Response $response) {

    $content = json_encode(file('noms.txt', FILE_IGNORE_NEW_LINES));
    $response->setContent($content);
    $response->setStatusCode(200);
    return $response;
 
});
$router->addRoute('POST', '/noms', function (Request $request, Response $response) {

    $data = json_decode($request->getContent(), true);
    file_put_contents('noms.txt', $data['nom'] . "\n", FILE_APPEND);
    $response->setStatusCode(200);
    return $response;
 
});
$router->addRoute('DELETE', '/noms', function (Request $request, Response $response) {
 
    $index = $_GET['id'];
    $arr = file('noms.txt');
    unset($arr[$index]);
    $fp = fopen('noms.txt', 'w+');
    foreach($arr as $line) { 
        fwrite($fp,$line);
    };
    fclose($fp);
    $response->setStatusCode(200);
    return $response;
 
});
 
// On traite la requête
$dispatcher = $router->getDispatcher();
$request = Request::createFromGlobals();
$response = $dispatcher->dispatch($request->getMethod(), $request->getPathInfo());
 
// On renvoie la réponse
$response->send();

J’ai prévu un code simplifié que je ne détaillerai pas parce que ce n’est pas l’objet de ce cours.

Il ne nous manque plus qu’un petit fichier texte pour mémoriser les noms (ça sera plus simple que de mettre en œuvre une base de données ). Prévoyez quelques noms au départ :

Dupont
Durand

Nommez ce fichier noms.txt et positionnez le aussi dans le serveur.

Au final vous devez avoir ceci :

img06

Maintenant tout est prêt côté serveur, on va pouvoir revenir à Vue.js côté client…

L’application

Voici le code complet de la page :

<!DOCTYPE html>
<html lang="fr">

  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Test vue.js</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">
  </head>

  <body>

    <div class="container">
      <br>

      <script type="text/x-template" id="panneau-template">
        <div class="alert alert-success" v-show="success">
          <button type="button" class="close" v-on:click="closeSuccess()">
            <span>×</span>
          </button>
          Serveur mis à jour avec succès ! 
        </div>
        <div class="alert alert-danger" v-show="danger">
          <button type="button" class="close" v-on:click="closeDanger()">
            <span>×</span>
          </button>
          Echec de la communication avec le serveur ! 
        </div>
        <div class="panel panel-primary">
          <div class="panel-heading">Liste des noms</div>        
          <table class="table table-bordered table-striped">
            <thead>
              <tr>
               <th class="col-sm-10">Nom</th>
               <th class="col-sm-2"></th>
              </tr>
            </thead>
            <tbody>
              <tr v-for="nom in noms">
                <td>{{ nom }}</td>
                <td><button class="btn btn-warning btn-block" v-on:click="supprimer($index)">Supprimer</button></td>  
              </tr>  
              <tr>
                <td><input type="text" class="form-control" v-model="inputNom" placeholder="Nom"></td>
                <td colspan="1"><button class="btn btn-primary btn-block" v-on:click="ajouter()">Ajouter</button></td>
              </tr>
            </tbody>       
          </table>
        </div>  
      </script>

      <div id="tuto"></div>

    </div>

    <script src="http://cdn.jsdelivr.net/vue/1.0.10/vue.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue-resource/0.1.17/vue-resource.min.js"></script>

    <script>

      var resource = null;

      new Vue({
        el: '#tuto',
        data: {
          noms: [],
          inputNom: '',
          success: false,
          danger: false
        },
        template: '#panneau-template',
        ready: function() {
          resource = this.$resource('serveur/noms');
          resource.get(function (data) {
            this.noms = data;
          }).error(function () {
            this.danger = true;
          })
        },
        methods: {
          ajouter: function() {
            resource.save({nom: this.inputNom}, function () {
              this.success = true;
              this.noms.push(this.inputNom);
              this.inputNom = '';
            }).error(function () {
              this.danger = true;
            })
          },
          supprimer: function(index) {
            resource.delete({id: index}, function () {
              this.success = true;
              this.noms.splice(index, 1);
            }).error(function () {
              this.danger = true;
            })
          },
          closeSuccess: function() {
            this.success = false;
          },
          closeDanger: function() {
            this.danger = false;
          }
        }
      });

    </script>

  </body>

</html>

Pour que l’application fonctionne il faut bien référencer l’url du serveur au niveau de cette ligne :

resource = this.$resource('serveur/noms');

Vous devez adapter l’url selon la configuration de vos dossiers. Ici j’ai mis le serveur dans un dossier enfant « serveur ».

Au chargement si tout se passe bien on obtient :

img07

On retrouve les deux noms qu’on a entrés dans le fichier noms.txt dans le serveur (ou ailleurs dans une base de données si vous avez adopté une autre approche pour votre API).

Ajouter un nom

On peut ajouter un nom avec la zone de texte prévue, ce nom vient se positionner dans la liste et un message d’information apparaît :

img08

On peut faire disparaître la barre d’information en cliquant sur la petite croix.

Si on regarde dans le fichier noms.txt on trouve bien le nom en fin de liste :

Dupont
Durand
Carlito

Supprimer un nom

De la même manière on peut supprimer un nom :

img09

On a à nouveau la barre d’information et dans le fichier du serveur on a bien la disparition du nom :

Dupont
Carlito

Notre application fonctionne correctement . Il ne nous reste plus qu’à voir tout ça de plus près…

Le template

J’ai prévu de référencer le template :

template: '#panneau-template',

C’est plus propre. On retrouve donc ce template dans le flux du HTML mais il est géré uniquement par Javascript :

<script type="text/x-template" id="panneau-template">
  ... Le template ici
</script>

La partie qui concerne la gestion l’affichage des noms correspond à ce que nous avons déjà vu dans ce cours avec des directives v-forv-model et v-on :

<div class="panel panel-primary">
  <div class="panel-heading">Liste des noms</div>        
  <table class="table table-bordered table-striped">
    <thead>
      <tr>
       <th class="col-sm-10">Nom</th>
       <th class="col-sm-2"></th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="nom in noms">
        <td>{{ nom }}</td>
        <td><button class="btn btn-warning btn-block" v-on:click="supprimer($index)">Supprimer</button></td>  
      </tr>  
      <tr>
        <td><input type="text" class="form-control" v-model="inputNom" placeholder="Nom"></td>
        <td colspan="1"><button class="btn btn-primary btn-block" v-on:click="ajouter()">Ajouter</button></td>
      </tr>
    </tbody>       
  </table>
</div>

Pour les barres d’information on a du code classique de Bootstrap :

<div class="alert alert-success" v-show="success">
  <button type="button" class="close" v-on:click="closeSuccess()">
    <span>×</span>
  </button>
  Serveur mis à jour avec succès ! 
</div>
<div class="alert alert-danger" v-show="danger">
  <button type="button" class="close" v-on:click="closeDanger()">
    <span>×</span>
  </button>
  Echec de la communication avec le serveur ! 
</div>

Par contre on n’utilise pas la librairie Javascript de Bootstrap pour le bouton de fermeture puisqu’on dispose déjà de vue.js qui nous offre des possibilités plus larges. C’est pour cette raison que j’ai prévu la directive v-on pour appeler une méthode de la VueModèle pour gérer la visibilité des barres.

La ressource

Pour mettre en place le service de ressource et demander au serveur la liste de noms on a ce code :

ready: function() {
  resource = this.$resource('serveur/noms');
  resource.get(function (data) {
    this.noms = data;
  }).error(function () {
    this.danger = true;
  })
},

Nous n’avions pas encore rencontré la fonction ready. Il faut un certain temps pour que la VueModèle soit créée et le template compilé. Avant de communiquer avec le serveur on va attendre d’être sûr que tout cela est réalisé. Lorsque c’est le cas vue.js appelle la fonction ready si elle existe. Vous avez toutes les information concernant le cycle de vie d’une VueModèle dans la documentation.

La première chose qu’on fait est de créer la ressource :

resource = this.$resource('serveur/noms');

On utilise ensuite la méthode get pour créer la requête pour l’API :

resource.get(function (data) {
  this.noms = data;
}).error(function () {
  this.danger = true;
})

On dispose ensuite de blocs de code en cas de réussite ou d’échec. A ce stade une requête avec le verbe GET est envoyé à l’API avec l’url « …/noms ». L’API lit le fichier des noms et renvoie les noms :

["Dupont","Durand"]

Il suffit de renseigner la propriété noms de la VueModèle pour avoir l’affichage des noms :

this.noms = data;

En cas d’échec on met la propriété danger à true :

this.danger = true;

Dans le template est prévu une directive v-show avec cette propriété :

<div class="alert alert-danger" v-show="danger">

Ce qui permet de gérer simplement son affichage :

img10

Pour cacher la barre il est fait appel à la méthode closeDanger :

closeDanger: function() {
  this.danger = false;
}

Ajouter un nom

On a prévu un bouton dans le template pour ajouter un nom :

<td colspan="1">
    <button class="btn btn-primary btn-block" v-on:click="ajouter()">
        Ajouter
    </button>
</td>

Un clic sur le bouton appelle la méthode ajouter :

ajouter: function() {
  resource.save({nom: this.inputNom}, function () {
    this.success = true;
    this.noms.push(this.inputNom);
    this.inputNom = '';
  }).error(function () {
    this.danger = true;
  })
},

Cette fois on utilise la méthode save de la ressource. Le premier paramètre sert à transmettre l’information, en l’occurrence le nom ajouté.

Cette fois on a un verbe POST avec comme contenu de requête cette information JSON :

{"nom":"Tartine"}

L’API stocke le nom et se contente de renvoyer un statut 200 pour dire que tout s’est bien passé. On met alors la liste et la zone de saisie à jour et la barre d’information de réussite est affichée.

En cas d’échec on affiche la barre d’erreur comme on l’a vu précédemment.

Supprimer un nom

Pour chaque nom on dispose d’un bouton de suppression :

<td>
    <button class="btn btn-warning btn-block" v-on:click="supprimer($index)">
        Supprimer
    </button>
</td>

On appelle la méthode supprimer en précisant l’index du nom :

supprimer: function(index) {
  resource.delete({id: index}, function () {
    this.success = true;
    this.noms.splice(index, 1);
  }).error(function () {
    this.danger = true;
  })
},

On transmet cette fois l’identifiant du nom avec une requête DELETE de cette forme : « …/noms?id=2 ». L’API supprime le nom de la liste et renvoie un statut 200. On affiche la barre de réussite et on supprime le nom de la liste.

En cas d’erreur c’est exactement comme pour le cas de l’ajout d’un nom.

Notre application est évidemment très simple et pourrait  être améliorée et complétée sur bien des aspects mais elle vous donne de bonnes bases pour l’utilisation de ce plugin.

En résumé

  • Vue.js dispose de quelques plugins et devrait élargir rapidement sa panoplie

  • Le plugin vue-resource permet trsè simplement d’établir une communication avec un serveur

  • il est facile de mettre en place une application cliente qui fait appel à une API

Laisser un commentaire