Skip to content

Étape 12 — Recherche par nom

Objectifs

  • Créer une donnée calculée (computed) qui filtre les Pokémon par nom
  • Utiliser v-model pour lier un champ de texte à une variable réactive
  • Afficher un message quand aucun résultat ne correspond

Prérequis — Point de départ

Cette étape nécessite d'avoir terminé les étapes 1 à 11 (séquences 1-3). Si votre code n'est pas à jour, vous pouvez repartir de la branche etape-12-start :

bash
# Ajouter le dépôt du prof (une seule fois)
git remote add prof https://github.com/fallinov/esig-141-pokedex-vuetify.git
# Récupérer les branches et basculer
git fetch prof
git checkout -b etape-12-start prof/etape-12-start
npm install

Voir la branche sur GitHub

Contexte

La page d'accueil affiche tous les Pokémon. On veut ajouter un champ de recherche pour filtrer par nom en temps réel, sans modifier les données du store.

Encart oral — computed : donnée calculée réactive

Un computed est une donnée dérivée qui se recalcule automatiquement quand ses dépendances changent.

js
const filteredList = computed(() => {
  return list.value.filter(item => item.name.includes(query.value))
})
  • Si query change → filteredList est recalculé automatiquement
  • Si list change → filteredList est recalculé automatiquement
  • Vue.js ne recalcule que quand c'est nécessaire (mise en cache)
  • On ne modifie jamais les données sources — on crée une vue filtrée

Tâches

1. Ajouter le champ de recherche

Dans src/pages/index.vue, ajoutez une variable réactive et un champ de texte au-dessus de la grille :

ComposantRôleDocumentation
<v-text-field>Champ de saisie avec label, icône, bouton effacerText Fields

Dans le <script setup>, créez la variable réactive :

js
const searchQuery = ref('')

Dans le <template>, ajoutez une <v-row> entre le titre et la grille de cartes :

vue
<v-row class="mb-4">
  <v-col cols="12" sm="6" md="4">
    <v-text-field
      v-model="searchQuery"
      label="Rechercher un Pokémon"
      prepend-inner-icon="mdi-magnify"
      clearable
      hide-details
      variant="outlined"
      density="compact"
    />
  </v-col>
</v-row>
  • v-model="searchQuery" : lie la valeur du champ à la variable réactive (binding bidirectionnel)
  • clearable : ajoute un bouton ✕ pour vider le champ
  • prepend-inner-icon : affiche une icône de recherche à l'intérieur du champ
  • hide-details : masque l'espace réservé aux messages de validation
  • variant="outlined" : style avec bordure
  • density="compact" : réduit la hauteur du champ

2. Créer le computed de filtrage

Dans le <script setup>, créez un computed qui filtre les Pokémon par nom :

js
const filteredBySearch = computed(() => {
  if (!searchQuery.value) return pokemonStore.pokemons
  const query = searchQuery.value.toLowerCase()
  return pokemonStore.pokemons.filter(pokemon =>
    pokemon.name.toLowerCase().includes(query),
  )
})

Points importants :

  • Si le champ est vide → on retourne tous les Pokémon (pas de filtre)
  • toLowerCase() rend la recherche insensible à la casse ("pika" trouve "Pikachu")
  • includes() cherche une sous-chaîne ("chu" trouve "Pikachu")
  • On ne modifie jamais pokemonStore.pokemons — le computed crée un nouveau tableau

3. Utiliser le computed dans le template

Remplacez pokemonStore.pokemons par filteredBySearch dans le v-for de la grille :

vue
<v-col
  v-for="pokemon in filteredBySearch"
  :key="pokemon.id"
  cols="12"
  sm="6"
  md="4"
  lg="3"
>

4. Afficher un message si aucun résultat

Ajoutez un <v-alert> qui s'affiche quand la liste filtrée est vide :

vue
<v-alert
  v-if="filteredBySearch.length === 0"
  type="info"
  variant="tonal"
  class="mb-6"
>
  Aucun Pokémon ne correspond à votre recherche.
</v-alert>

<v-row v-else>
  <!-- la grille de cartes existante -->
</v-row>

Projet perso — À faire en parallèle

Dans votre projet individuel :

  1. Ajoutez un champ de recherche avec v-model
  2. Créez un computed qui filtre vos données par nom
  3. Utilisez le computed dans votre v-for
  4. Gérez le cas "aucun résultat"

Tests

  • Taper "pika" affiche uniquement les Pokémon dont le nom contient "pika"
  • Effacer le champ réaffiche tous les Pokémon
  • La recherche est insensible à la casse ("PIKA" = "pika" = "Pika")
  • Si aucun Pokémon ne correspond, le message "Aucun Pokémon ne correspond" s'affiche
  • Le bouton ✕ (clearable) vide le champ et réaffiche tous les Pokémon
  • 0 erreurs dans la console

Solution

src/pages/index.vue
vue
<template>
  <v-container>
    <h1 class="text-h3 text-center my-6">
      Pokédex
      <span class="text-subtitle-1">({{ pokemonStore.totalPokemons }})</span>
    </h1>

    <!-- Barre de recherche -->
    <v-row class="mb-4">
      <!--
      Champ de recherche par nom
        * v-model lie la valeur au ref searchQuery (binding bidirectionnel)
        * clearable ajoute un bouton pour vider le champ
        * prepend-inner-icon ajoute une icône à l'intérieur du champ
      -->
      <v-col cols="12" sm="6" md="4">
        <v-text-field
          v-model="searchQuery"
          label="Rechercher un Pokémon"
          prepend-inner-icon="mdi-magnify"
          clearable
          hide-details
          variant="outlined"
          density="compact"
        />
      </v-col>
    </v-row>

    <!--
    Message si aucun résultat
      * v-if vérifie que la liste filtrée est vide
    -->
    <v-alert
      v-if="filteredBySearch.length === 0"
      type="info"
      variant="tonal"
      class="mb-6"
    >
      Aucun Pokémon ne correspond à votre recherche.
    </v-alert>

    <!--
    Grille de cartes Pokémon
      * On itère sur filteredBySearch (et non pokemonStore.pokemons)
      * Ainsi la grille se met à jour automatiquement quand on tape dans le champ
    -->
    <v-row v-else>
      <v-col
        v-for="pokemon in filteredBySearch"
        :key="pokemon.id"
        cols="12"
        sm="6"
        md="4"
        lg="3"
      >
        <pokemon-card :pokemon="pokemon" />
      </v-col>
    </v-row>
  </v-container>
</template>

<script setup>
import { usePokemonStore } from '@/stores/pokemonStore'
import PokemonCard from '@/components/PokemonCard.vue'

// Récupération du store Pokémon
const pokemonStore = usePokemonStore()

// Variable réactive pour la recherche
// ref() est auto-importé grâce à unplugin-auto-import
const searchQuery = ref('')

/**
 * Computed : filtre les Pokémon par nom
 * Se recalcule automatiquement quand searchQuery change
 * Ne modifie jamais les données du store (pokemonStore.pokemons reste intact)
 */
const filteredBySearch = computed(() => {
  // Si le champ est vide, retourner tous les Pokémon
  if (!searchQuery.value) return pokemonStore.pokemons

  // Convertir la recherche en minuscules pour une comparaison insensible à la casse
  const query = searchQuery.value.toLowerCase()

  // Filtrer : ne garder que les Pokémon dont le nom contient la recherche
  return pokemonStore.pokemons.filter(pokemon =>
    pokemon.name.toLowerCase().includes(query),
  )
})
</script>

Commit

bash
git add -A
git commit -m "feat: recherche Pokémon par nom avec computed"

Documentation pour les cours de développement web