Introduction à Vue.js — construisons une Memebox

Publié le 7 décembre 2017 par Tom Panier | front

Cet article est publié sous licence CC BY-NC-SA

Si vous êtes un tant soit peu comme moi, il y a fort à parier que votre communication en ligne est fortement axée sur le visuel. Mes collègues diraient plutôt que j’inonde les canaux Slacks et les merge requests avec des GIFs stupides, au lieu de m’exprimer avec des mots. Question de point de vue, mais la finalité reste la même : j’ai un besoin fréquent et récurrent d’accès à une collection d’images qui me servent au quotidien.

J’ai donc décidé de développer un petit outil pour me faciliter la vie (et pourrir celle de mes collègues), et comme je suis une personne aimable (sisi), j’ai également décidé d’en profiter pour vous donner un premier aperçu de Vue.js, un framework JavaScript front-end qui prend une place de plus en plus importante dans les projets de Synbioz. C’est parti !

Le besoin

L’outil susmentionné consistera en une simple page Web présentant les contenus sous forme de grille ; au clic sur l’un des éléments, son URL sera ajoutée au presse-papiers afin de permettre de la coller n’importe où immédiatement.

On pourrait imaginer d’autres fonctionnalités (tagging et recherche parmi les contenus, qui pourraient s’avérer utiles à mesure que la taille de la collection s’accroît), mais autant commencer par une version simple.

Les bases

Pour les besoins de l’article, je vais tâcher de conserver une certaine simplicité dans la mise en place de l’outil. On aura donc un fichier HTML, un fichier JS et un fichier CSS — pour une application real-world, vous aurez sûrement plus de fichiers que ça.

Voici notre squelette de page HTML :

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8" />
  <title>MemeBox</title>
  <link rel="stylesheet" href="app.css" />
</head>

<body>
  <div id="app"></div>
  <script src="https://unpkg.com/vue"></script>
  <script src="app.js"></script>
</body>

</html>

Notre collection d’images sera stockée en tant que tableau d’URLs, directement dans le code JavaScript. Je vous conseille d’en prévoir un certain nombre, histoire d’avoir un rendu un peu sympa ; si vous êtes en panne d’inspiration, vous pouvez toujours utiliser un service comme Placekitten.

const images = [
  "http://path/to/some/image.gif",
  "http://path/to/another/image.jpg",
  // ...
];

On va également ajouter un peu de CSS pour partir sur de bonnes bases :

* {
  box-sizing: border-box;
}

html,
body {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
}

body {
  background-color: #333333;
  font-family: sans-serif;
}

Mise en page

Première étape : afficher nos images ! On va envelopper chacune d’elles d’une div afin d’avoir plus de latitude niveau styles, et aussi d’anticiper la fonctionnalité de survol/clic que nous mettrons en place par la suite. De par la façon dont nous avons inclus Vue.js dans notre page, nous pouvons utiliser son markup « custom » directement à l’intérieur de notre page HTML :

<div id="app" class="memebox">
  <div
    v-for="image in images"
    class="meme"
  >
    <img :src="image" />
  </div>
</div>

Ici, v-for permet d’itérer sur notre collection ; l’élément sur lequel il se trouve sera donc répété pour chacune de nos images. Quant au : au début de :src, il spécifie que la valeur de l’attribut doit être interprétée en tant que code JavaScript (il s’agit du nom de notre variable de boucle) plutôt que comme une simple chaîne. Il s’agit de sucre pour la syntaxe complète, à savoir v-bind:src="...".

Mais d’où vient la variable images sur laquelle itère notre boucle ? Il nous faut la définir en déclarant notre application côté JavaScript :

const images = [
  // ...
];

new Vue({
  el: "#app",
  data: { images }
});

Nous en profitons pour indiquer au framework sur quel élément HTML « racine » il doit travailler, à savoir notre div#app.

Afin d’obtenir l’affichage en grille tel que souhaité, nous allons utiliser Flexbox côté CSS :

.memebox {
  display: flex;
  flex-flow: row wrap;
}

.meme {
  flex: 1 0 10%;
  min-width: 175px;
  min-height: 175px;
}

.meme img {
  max-width: 100%;
}

Et voilà ! En deux coups de cuiller à pot, nos images se rangent bien sagement sur une grille, responsive par-dessus le marché.

Le seul inconvénient est que nos images sont rarement carrées par défaut, laissant ainsi apparaître des marges peu esthétiques autour de certaines d’entre elles. Nous pourrions utiliser JavaScript pour recalculer leurs dimensions à la volée, mais cela risquerait d’être inutilement coûteux ; nous allons donc ruser en supprimant les balises img et en utilisant nos images en tant que backgrounds, ce qui nous donnera plus de liberté sur leur rendu :

<div id="app" class="memebox">
  <div
    v-for="image in images"
    class="meme"
    :style="{ backgroundImage: 'url(' + image + ')' }"
  ></div>
</div>

Notez la syntaxe spéciale supportée par Vue.js pour déclarer les styles inline comme un objet JavaScript, et l’utilisation idoine de la camelCase pour les clés.

Quelques modifications sont également nécessaires côté styles :

.meme {
  flex: 1 0 10%;
  position: relative;
  min-width: 175px;
  min-height: 175px;
  background-position: center;
  background-size: cover;
}

Interaction utilisateur

Maintenant que nos images s’affichent comme on le souhaite, voyons comment rendre tout ça « utile » !

On désire copier dans le presse-papiers l’URL d’une image lorsqu’on clique dessus ; on va commencer par voir si on peut l’afficher dans la console, en définissant une méthode sur notre application Vue.js :

new Vue({
  el: "#app",
  data: { images },
  methods: {
    copy(image) {
      console.log(image);
    }
  }
});

Il ne nous reste plus qu’à ajouter le binding adéquat côté template :

<div id="app" class="memebox">
  <div
    v-for="image in images"
    class="meme"
    :style="{ backgroundImage: 'url(' + image + ')' }"
    @click="copy(image)"
  ></div>
</div>

Les paramètres commençant par @ sont des event handlers ; une fois de plus, il s’agit de sucre pour la syntaxe complète, à savoir v-on:click="...".

Désormais, lorsqu’on clique sur une de nos images, on voit bien son URL être logguée en console. Afin de réellement mettre cette dernière dans le presse-papiers, nous allons devoir utiliser une astuce qui consiste à créer temporairement un élément textarea. L’écriture de cette fonction n’est pas ce qui nous intéresse ici, je vous la fournis donc :

function copyToClipboard(text) {
  const textArea = document.createElement("textarea");
  textArea.value = text;
  document.body.appendChild(textArea);
  textArea.select();
  document.execCommand("copy");
  document.body.removeChild(textArea);
}

Il suffit dès lors de l’utiliser en lieu et place de console.log :

new Vue({
  el: "#app",
  data: { images },
  methods: {
    copy(image) {
      copyToClipboard(image);
    }
  }
});

Un coup de polish

Notre (petite) application commence à prendre forme, et rend le service attendu. Nous allons terminer en la rendant graphiquement un peu plus sympathique à utiliser, en utilisant un état de survol :

.meme:before {
  content: "Copy URL";
  display: none;
  position: absolute;
  z-index: 1;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background-color: rgba(0, 0, 0, 0.75);
  line-height: 175px;
  text-align: center;
  color: white;
  cursor: pointer;
}

.meme:hover:before {
  display: block;
}

Sympa, non ? Mais on peut faire encore mieux, en gérant une petite notion d’état sur notre application, afin de changer le texte après le clic (et de le restaurer lorsque la souris quitte l’image) :

new Vue({
  el: "#app",
  data: {
    images,
    clicked: false
  },
  methods: {
    copy(image) {
      copyToClipboard(image);
      this.clicked = true;
    },
    hover() {
      this.clicked = false;
    }
  }
});

Nous ajoutons donc une propriété clicked valant initialement false, passée à true lorsqu’on clique sur une image, et passée de nouveau à false lors de l’appel à notre nouvelle méthode hover, qu’on va attacher à l’évènement mouseleave de nos images :

<div id="app" class="memebox">
  <div
    v-for="image in images"
    class="meme"
    :class="{ 'meme-clicked': clicked }"
    :style="{ backgroundImage: 'url(' + image + ')' }"
    @click="copy(image)"
    @mouseleave="hover"
  ></div>
</div>

Notez l’utilisation conjointe de class et :class (respectivement statique et dynamique), qui seront mixés par Vue.js à la compilation du template. Notez également que le second utilise une syntaxe similaire à celle des styles inline telle que vue plus haut. Un dernier coup de tournevis sur le CSS :

.meme-clicked:before {
  content: "Copied!";
}

Le mot de la fin

Le tour est joué ! Grâce à Vue.js, nous avons pu mettre sur pied cette petite application très rapidement et simplement, et j’espère vous avoir donné envie d’en apprendre plus sur ce framework, dont nous reparlerons très certainement à l’avenir. Stay tuned!


L’équipe Synbioz.
Libres d’être ensemble.