Aller au contenu

Client AJAX - Fetch

AJAX est une méthode pour réaliser des requêtes HTTP de manière asynchrone (et donc non bloquante) à un serveur web. Les données ainsi reçues seront accessibles uniquement en JavaScript, et non pas affichées par le navigateur (comme c’est le cas avec une requête classique).

Architecture AJAX

L’intérêt principal est de pouvoir ajouter du contenu dans les pages, et cela sans avoir à recharger l’intégralité du site. L’exemple le plus parlant est celui d’une application carto web :

http://openstreetmap.org

  • HTML + CSS pour la structure et la présentation
  • PHP + MySQL pour extraire les images de la zone qui nous intéresse (l’écran)
  • JavaScript pour l’intéractivité (clic & drag)

Quand l’utilisateur fait une recherche, les données doivent être récupérées coté serveur, afin de mettre à jour la carte avec les résultats.

Tout cela s’effectue de manière asynchrone. C’est à dire que les requêtes sont initiées, mais que le résultat de chaque requête sera accessible dans le futur, sans savoir à l’avance quand précisément. Et surtout, sans bloquer l’interface utilisateur, ni bien entendu le code à éxécuter. C’est ce dernier détail qui est le plus délicat à gérer en programmation, car il faut bien penser à attendre le résultat avant d’effectuer des actions.

Les données récupérées seront le plus souvent au format JSON ou XML. On parle alors de service web pour la réponse du serveur.

Faire une requête AJAX#

Pour faire une requête, on utilisera l’API Fetch et la fonction globale fetch().

fetch() est basée sur les promesses JavaScript, et notamment la méthode .then() qui permet d’attendre le résultat asynchrone de la requête avant d’effectuer une action. Cette méthode prend une fonction en paramètre, dont le seul paramètre est le résultat de la promesse précédente.

Requête simple GET

fetch('page.php')
.then(function (result) {
  // retourne le résultat binaire en json
  return result.json();
})
.then(function (result) {
  // result (le résultat au format JSON: un objet JS)
})

Requête simple GET en ES6 (fonctions fléchées)

// pour du JSON (et en ES6 (arrow functions))
fetch('page.php')
.then(result => result.json())
.then(result => {
  // result (le résultat au format JSON: un objet JS)
})

Exemples de requêtes

fetch('page.php')
fetch('/page')
fetch('http://example.com/page')
fetch('page.php?nom=toto') // passage de variable

Exercice - Service Web Base Adresse#

On souhaite réaliser un formulaire qui permette à l’utilisateur de taper une adresse et de voir les points sur une carte.

Cet exercice se réalise à la suite de l’exercice Première carte + Géolocalisation

  • ajoutez une <div id="entete"> en haut de page et utilisez CSS Grid pour afficher l’entête au dessus de la carte (entête de 100px de haut, la carte prends l’espace restant)
  • dans l’entête, ajoutez un formulaire avec un champ de recherche et un bouton de validation (vous pouvez styler un peu avec des composants Bootstrap)
  • instanciez une nouvelle application Vue
  • lors de la validation du formulaire (évènement @submit)
    • empêchez l’action par défaut (@submit.prevent)
    • récupérez la valeur saisie par l’utilisateur (utilisez une propriété dans data et v-model)
    • interrogez l’API de géocodage d’adresse.data.gouv.fr: http://api-adresse.data.gouv.fr/search/?q= (via AJAX, en mode GET, variables contenues dans l’URL), le retour est au format GeoJSON
    • positionnez les marqueurs correspondants aux résultats sur la carte

Ensuite :

  • enlevez les marqueurs avant d’en ajouter de nouveaux
    • utilisez un L.featureGroup et sa méthode clearLayers() (ou directement un L.GeoJSON qui hérite de L.featureGroup)
  • adaptez l’emprise de la carte à l’emprise des données affichées (monFeatureGroup.getBounds(), map.fitBounds(bounds))

Requêtes POST#

Pour l’envoi d’une requête HTTP de type POST, voici quelques exemples :

let donnees = new FormData();
donnees.append('nom', 'toto');
donnees.append('prenom', 'tata');

fetch('page.php', {
  method: 'post',
  body: donnees
})
.then(r => r.json())
.then(r => {
  console.log(r)
})
let donnees = { nom: 'toto', prenom: 'tata' };

fetch('page.php', {
  method: 'post',
  body: JSON.stringify(donnees)
  headers: {
    'Content-type': 'application/json'
  }
})
.then(r => r.json())
.then(r => {
  console.log(r)
})

// Coté PHP: récupérer le contenu envoyé avec
// json_decode(file_get_contents('php://input'), true);

// FlightPHP
// Flight::request()->data->nom;
let donnees = 'nom=toto&prenom=tata';

fetch('page.php', {
  method: 'post',
  body: donnees,
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded'
  }
})
.then(r => r.json())
.then(r => {
  console.log(r)
})

Aller plus loin avec l’API Fetch

Note

Si l’on ne souhaite pas réécrire à chaque fois la syntaxe complète, nous pouvons créer notre propre fonction, spécifiquement pour du JSON :

function fetchJSON(url, params) {
  return fetch(url, params).then(r => r.json());
}
Celle-ci pourra être appelée directement
fetchJSON(url).then(result => {
  // result (le résultat au format JSON: un objet JS)
})

Objet XHR

Objet XHR#

Historiquement, c’est l’objet XMLHttpRequest de JavaScript qui permettait de réaliser nos fameuses requêtes AJAX. Voici donc des exemples de code utilisant l’objet XMLHttpRequest au lieu de fetch

Requête simple GET

// creation de l’objet
var ajax = new XMLHttpRequest();
// méthode HTTP utilisée et fichier à charger
ajax.open('GET', 'page.php');
// on écoute l’évènement load de la requête
ajax.addEventListener('load',  function () {
    // quand c’est fini, on affiche le résultat
    console.log(ajax.response)
});
// envoi la requête
ajax.send();

Requêtes POST

var data = "nom=toto&prenom=tata";

var ajax = new XMLHttpRequest();
ajax.open('POST', 'page.php');
ajax.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
ajax.addEventListener('load',  function () {
    console.log(ajax.response)
});
ajax.send(data);
var data = new FormData();
data.append('nom', 'toto');
data.append('prenom', 'tata');

var ajax = new XMLHttpRequest();
ajax.open('POST', 'page.php');
ajax.addEventListener('load',  function () {
    console.log(ajax.response)
});
ajax.send(data);
var data = JSON.stringify({ nom: 'toto', prenom: 'tata' });

var ajax = new XMLHttpRequest();
ajax.open('POST', 'page.php');
ajax.addEventListener('load',  function () {
    console.log(ajax.response)
});
ajax.send(data);

// Coté PHP: récupérer le contenu envoyé avec
// json_decode(file_get_contents('php://input'), true);

Réponse#

Il est possible de modifier le type de la réponse, notamment si le fichier PHP retourne du JSON

ajax.responseType = 'json';

L’objet ajax.response sera alors déjà un objet JSON, sans besoin de le parser.

Erreurs#

Pour gérer les erreurs, vous pouvez écouter l’évènement error. Mais attention, une requête avec un code de retour autre que 200 ne génère pas une erreur. Pour gérer ce cas, testez ajax.status

// Erreur de réseau
ajax.addEventListener('error', function () {
  // erreur
});

// Erreur HTTP
ajax.addEventListener('load', function () {
  if (ajax.status != 200) {
    // erreur
  }
});

Problématiques#

  • AJAX est entièrement dépendant de JavaScript (si JS est désactivé, ne se charge pas ou autre, l’application web ne fonctionne pas du tout). Cela pose de gros problèmes d’accessibilité.
  • AJAX ne modifie pas l’historique de votre navigateur. Si vous basez une navigation entièrement sur AJAX, il faut alors modifier l’historique avec l’API HTML5 dédiée.
  • AJAX pose également des contraintes de sécurité. Il n’est pas possible de réaliser des requêtes si elles s’effectuent sur des origines différentes, c’est le Same Origin Policy. Un serveur peut néanmoins autoriser les requêtes provenant d’origines diverses avec CORS: Cross-Origin Resource Sharing. Voir également différents exemples de code en fonction du serveur

Exercice - Autocomplétion AJAX#

On souhaite réaliser une architecture complète qui permette de rechercher une commune de France, et de la visualiser sur une carte web. Pour cela, l’utilisateur devra pouvoir écrire le nom d’une commune, et un service web se chargera de renvoyer une liste de communes basées sur cette recherche. Enfin un clic sur l’une des communes récupéréra son emprise géographique que l’on visualisera sur une carte.

Cet exercice se réalise à la suite des exercices Première carte + Géolocalisation et Service web base adresse

Première étape, nous allons réaliser le service web (première partie) :

  • créez une nouvelle route "POST /villes" (pas besoin de vue associée ici)
  • récupérez la valeur envoyée dans la requête avec $_POST['recherche'] ou Flight::request()->data['recherche'] (nous partons du principe que la donnée devra être envoyée sous la clé recherche)
  • si cette recherche n’est pas vide, alors
    • faites une requête qui récupère le nom et le numéro insee des communes qui commencent par la chaîne reçue, limité à 10 résultats
    • utilisez la connexion à la BDD geobase
    • créez un tableau PHP des données
    • retournez le tout en JSON
  • retournez un tableau vide si recherche est vide
<?php

Flight::route('POST /villes', function() {
    Flight::json(['id' => 123]);
});

?>
Données de connexion à la BDD geobase

Voici les infos pour la base de données MySQL

  • serveur : u2.ensg.eu
    • base : geobase
    • utilisateur : geo
    • mot de passe : '' (chaine de caractères vide)
    • tables (d’autres champs ne sont pas précisés ici):
      • regions (champs insee, nom)
      • departements (champs insee, region_insee, nom)
      • communes (champs insee, departement_insee, nom, surface, geometry)
  • ou export disponible

Attention, les clés étrangères sont basées sur le champ insee, qui est une chaine de caractères.

Coté client :

  • ajoutez une nouvelle liste <ul id="villes> dans l’entête par exemple, qui contiendra la liste des villes (voir le CSS associé en dessous)
  • ajoutez un nouvel événement lors de la saisie au clavier dans le champ texte (événement input)
    • exécutez une requête AJAX de type POST sur votre fichier PHP (web service) en envoyant la valeur actuelle du champ texte (clé recherche)
    • récupérez le résultat JSON de votre service web et stockez-le dans l’application Vue
  • utilisez ce tableau de villes pour construire le HTML correspondant dans <ul id="villes>
    • boucle v-for pour générer des balises <li> HTML
    • test v-if pour n’afficher que si la liste n’est pas vide
#villes {
    position: absolute;
    top: 100px;
    left: 0; right: 0;
    z-index: 5000;
    padding: 0;
    background: lightblue;
}
#villes li {
    padding: 10px;
    list-style-type: none;
}
#villes li:hover {
    background: white;
}

Enfin, pour récupérer l’emprise géométrique de notre commune, nous allons modifier notre service web :

  • modifiez votre route /villes, pour récupérer une éventuelle donnée insee_ville
  • récupérez la géométrie de la commune correspondant au numéro insee dans la BDD
    • champ geometry
    • utilisez la fonction ST_AsGeoJson(geometry) pour récupérer une chaîne geoJSON
    • convertissez la chaîne en objet avec json_decode($string)
    • utilisez AS en SQL pour plus de simplicité
    • retournez le tout en JSON

Puis, coté client :

  • ajoutez un événement click sur chaque <li>, en passant en paramètre le numéro insee de la commune
  • dans la fonction :
    • videz la liste des villes (pour enlever la liste lors du click)
    • faites appel à votre service web
    • intégrez la géométrie dans la carte, et adpatez l’emprise de votre carte