We're looking for a user researcher to understand the needs of developers and designers. Is this you or someone you know? Check out the post: https://mzl.la/2IGzdXS

Cette traduction est incomplète. Aidez à traduire cet article depuis l'anglais.

Comme dans le précédent article, les formulaires HTML peuvent envoyer une requête HTTP par énonciation. Mais les formulaires peuvent aussi préparer une requête HTTP à adresser via JavaScript. Cet article explore comment faire cela.

Un formulaire n'est pas toujours un <form>

Avec les applications Web ouvertes, il est de plus en plus courant d'utiliser des formulaires HTML autres que des formulaires à remplir par des personnes — de plus en plus de développeurs prennent le contrôle de la transmission des données.

Obtenir le contrôle de l'interface global

La soumission d'un formulaire HTML standard charge l'URL où les données sont envoyées, car la fenêtre du navigateur manipule une pleine page. Éviter de charger une pleine page peut amener plus de fluidité en masquant des clignotements et des lenteurs de réseau.

De nombreuses UI modernes n'utilisent les formulaires HTML que pour recueillir des données utilisateur. Lorsque l'utilisateur veut envoyer les données, l'application prend la main et transmet les données de manière asynchrone en arrière-plan, mettant à jour uniquement les parties de l'interface utilisateur nécessitant des modifications.

L'envoi asynchrone de données arbitraires est connu sous le nom AJAX, qui signifie "Asynchronous JavaScript And XML" (XML et JavaScript asynchrones).

Comment est-ce différent ?

AJAX utilise l'objet DOM XMLHttpRequest (XHR).Il peut construire des requêtes HTTP, les envoyer et retrouver leur résultats.

Note : Les techniques AJAX anciennes ne se fondaient pas forcément sur XMLHttpRequest. Par exemple, JSONP combiné à la fonction eval(). Cela fonctionne, mais n'est pas recommandé en raison de sérieux problèmes de sécurité. La seule raison d'en poursuivre l'utilisation est l'utilisation de navigateurs anciens qui ne prennent pas en charge XMLHttpRequest ou JSON, mais ce sont vraiment des navigateurs anciens ! Évitez ces techniques.

 

Historiquement, XMLHttpRequest a été conçu pour récupérer et envoyer du XML comme format d'échange. Cependant, JSON a remplacé XML et est de plus en plus courant aujourd'hui.

Mais ni XML ni JSON ne s'adaptent à l'encodage des demandes de données de formulaire. Les données de formulaire (application/x-www-form-urlencoded) sont constituées de listes de paires clé/valeur codées par URL. Pour la transmission de données binaires, la requête HTTP est transformée en données multipart/form‑data.

Si vous contrôlez le frontal (le code exécuté dans le navigateur) et l'arrière‑plan (le code exécuté sur le serveur), vous pouvez envoyer JSON/XML et les traiter comme vous le souhaitez.

Mais si vous voulez utiliser un service tiers, ce n'est pas si facile. Certains services n'acceptent que les données de formulaire. Il y a aussi des cas où il est plus simple d'utiliser les données du formulaire. Si les données sont des paires clé/valeur ou des données binaires brutes, des outils d'arrière‑plan existants peuvent les traiter sans code supplémentaire.

Comment envoyer de telles données ?

Envoi des données de formulaire

Il y a 3 façons d'envoyer des données de formulaire, allant des techniques existantes jusqu'à l'objet FormData plus récent. Examinons-les en détail.

Construire manuellement un XMLHttpRequest

XMLHttpRequest est la manière la plus sûre et la plus fiable de faire des requêtes HTTP. Pour envoyer des données de formulaire avec XMLHttpRequest, préparez les données par encodage URL et suivez les spécificités des requêtes de données de formulaire.

Note : Pour en savoir plus sur XMLHttpRequest, ces articles pourraient vous intéresser : Un article d'introduction à AJAX et un didacticiel plus fouillé à ce propos utilisant XMLHttpRequest.

Reconstruisons l'exemple précédent :

<button type="button" onclick="sendData({test:'ok'})">Cliquez ici !</button>

Comme vous pouvez le voir, le HTL n'a pas réellement changé. Mais, le JavaScript est totalement différent :

function sendData(data) {
  var XHR = new XMLHttpRequest();
  var urlEncodedData = "";
  var urlEncodedDataPairs = [];
  var name;

  // Transformez l'objet data en un tableau de paires clé/valeur codées URL.
  for(name in data) {
    urlEncodedDataPairs.push(encodeURIComponent(name) + '=' + encodeURIComponent(data[name]));
  }

  // Combinez les paires en une seule chaîne de caractères et remplacez tous
  // les espaces codés en % par le caractère'+' ; cela correspond au comportement
  // des soumissions de formulaires de navigateur.
  urlEncodedData = urlEncodedDataPairs.join('&').replace(/%20/g, '+');

  // Définissez ce qui se passe en cas de succès de soumission de données
  XHR.addEventListener('load', function(event) {
    alert('Ouais ! Données envoyées et réponse chargée.');
  });

  // Définissez ce qui arrive en cas d'erreur
  XHR.addEventListener('error', function(event) {
    alert('Oups! Quelque chose s\'est mal passé.');
  });

  // Configurez la requête
  XHR.open('POST', 'https://example.com/cors.php');

  // Ajoutez l'en-tête HTTP requise pour requêtes POST de données de formulaire 
  XHR.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');

  // Finalement, envoyez les données.
  XHR.send(urlEncodedData);
}

Voici le résultat en direct :

Note : Cette utilisation de XMLHttpRequest est assujettie à la politique « même origine » si vous voulez envoyer des données à un site web tiers. Pour les demandes d'origine croisée, vous aurez besoin d'un contrôle d'accès CORS et HTTP.

Utilisation de XMLHttpRequest et de l'objet FormData

Construire manuellement une requête HTTP peut devenir fastidieux. Heureusement, une spécification XMLHttpRequest récente fournit un moyen pratique et plus simple pour traiter les demandes de données de formulaire avec l'objet FormData.

L'objet FormData peut s'utiliser pour construire des données de formulaire pour la transmission ou pour obtenir les données des élément de formulaire de façon à gérer leur mode d'envoi. Notez que les objets FormData sont en écriture seule (« write only »), ce qui signifie que vous pouvez les modifier, mais pas récupérer leur contenu.

L'utilisation de cet objet est détaillée dans Utiliser les objets FormData, mais voici deux exemples :

Utiliser un objet FormData autonome

<button type="button" onclick="sendData({test:'ok'})">Cliquez ici !</button>

Vous devriez être familier de cet exemple HTML.

function sendData(data) {
  var XHR = new XMLHttpRequest();
  var FD  = new FormData();

  // Mettez les données dans l'objet FormData
  for(name in data) {
    FD.append(name, data[name]);
  }

  // Définissez ce qui se passe si la soumission s'est opérée avec succès
  XHR.addEventListener('load', function(event) {
    alert('Ouais ! Données envoyées et réponse chargée.');
  });

  // Definissez ce qui se passe en cas d'erreur
  XHR.addEventListener('error', function(event) {
    alert('Oups! Quelque chose s\'est mal passé.');
  });

  // Configurez la requête
  XHR.open('POST', 'https://example.com/cors.php');

  // Expédiez l'objet FormData ; les en-têtes HTTP sont automatiquement définies
  XHR.send(FD);
}

Voici le résultat directement :

Utiliser un objet FormData lié à un élément form

Vous pouvez également lier un objet FormData à un élément <form> et  créer ainsi un FormData représentant les données contenues dans le formulaire.

Le HTML est classique :

<form id="myForm">
  <label for="myName">Dites-moi votre nom :</label>
  <input id="myName" name="name" value="John">
  <input type="submit" value="Envoyer !">
</form>

Mais JavaScript sera de la forme :

window.addEventListener("load", function () {
  function sendData() {
    var XHR = new XMLHttpRequest();

    // Liez l'objet FormData et l'élément form
    var FD = new FormData(form);

    // Définissez ce qui se passe si la soumission s'est opérée avec succès
    XHR.addEventListener("load", function(event) {
      alert(event.target.responseText);
    });

    // Definissez ce qui se passe en cas d'erreur
    XHR.addEventListener("error", function(event) {
      alert('Oups! Quelque chose s\'est mal passé.');
    });

    // Configurez la requête
    XHR.open("POST", "https://example.com/cors.php");

    // Les données envoyées sont ce que l'utilisateur a mis dans le formulaire
    XHR.send(FD);
  }
 
  // Accédez à l'élément form …
  var form = document.getElementById("myForm");

  // … et prenez en charge l'événement submit.
  form.addEventListener("submit", function (event) {
    event.preventDefault();

    sendData();
  });
});

Voici le résultat en direct :

Vous pouvez même intervenir davantage dans le processus en utilisant la propriété elements du formulaire pour obtenir une liste de tous les éléments de données du formulaire et les gérer chacun individuellement dans le programme. Pour en savoir plus, voir l'exemple dans la Accessing the element list's contents in HTMLFormElement.elements.

Construire un DOM dans un iframe caché

La plus ancienne façon d'expédier des données de formulaire de manière asynchrone consiste à construire un formulaire avec l'API DOM, puis d'envoyer ses données dans un <iframe> caché. Pour accéder au résultat de votre envoi, il suffit de récupérer le contenu de l'élément <iframe>.

Avertissement : Évitez d'employer cette technique. Il y a des risques concernant la sécurité avec des services tierce-partie car vous laissez ouverte la possibilité d'une attaque par injection de script. Si vous utilisez HTTPS, il reste possible de perturber la politique de la même origine, ce qui peut rendre le contenu de l'élément <iframe> inatteignable. Toutefois, cette méthode peut être votre seule possibilité si vous devez prendre en charge des navigateurs très anciens.

Voici un exemple :

<button onclick="sendData({test:'ok'})">Cliquez ici !</button>
// Créer l'iFrame servant à envoyer les données
var iframe = document.createElement("iframe");
iframe.name = "myTarget";

// Puis, attachez l'iFrame au document principal
window.addEventListener("load", function () {
  iframe.style.display = "none";
  document.body.appendChild(iframe);
});

// Voici la fonction réellement utilisée pour expédier les données
// Elle prend un paramètre, qui est un objet chargé des paires clé/valeurs.
function sendData(data) {
  var name,
      form = document.createElement("form"),
      node = document.createElement("input");

  // Définir ce qui se passe quand la réponse est chargée
  iframe.addEventListener("load", function () {
    alert("Ouais ! Données envoyés.");
  });
    
  form.action = "http://www.cs.tut.fi/cgi-bin/run/~jkorpela/echo.cgi";
  form.target = iframe.name;

  for(name in data) {
    node.name  = name;
    node.value = data[name].toString();
    form.appendChild(node.cloneNode());
  }

  // Pour être envoyé, le formulaire nécessite d'être attaché au document principal.
  form.style.display = "none";
  document.body.appendChild(form);

  form.submit();

  // Une fois le formulaire envoyé, le supprimer.
  document.body.removeChild(form);
}

Voici le résultat en direct :

 

Gestion des données binaires

Si vous utilisez un objet FormData avec un formulaire qui inclut des widgets <input type="file">, les données seront traitées automatiquement. Mais pour envoyer des données binaires à la main, il y a un travail supplémentaire à faire.

Il existe de nombreuses sources de données binaires sur le Web moderne : FileReader, Canvas et WebRTC, par exemple. Malheureusement, certains anciens navigateurs ne peuvent pas accéder aux données binaires ou nécessitent des solutions de contournement compliquées. Ces cas hérités sont hors du champ d'application de cet article. Si vous voulez en savoir plus sur l'API FileReader, lisez Utiliser les fichiers des applications Web.

Envoyer des données binaires avec le support de FormData est direct. Utilisez la méthode append() et vous avez terminé. Si vous devez le faire à la main, c'est plus délicat.

Dans l'exemple suivant, nous utilisons l'API FileReader pour accéder aux données binaires et ensuite nous construisons à la main la demande de données du formulaire en plusieurs parties :

<form id="myForm">
  <p>
    <label for="i1">Données textuelles :</label>
    <input id="i1" name="myText" value="Quelques données textuelles">
  </p>
  <p>
    <label for="i2">Fichier de données :</label>
    <input id="i2" name="myFile" type="file">
  </p>
  <button>Envoyer !</button>
</form>

Comme vous pouvez le voir, le HTML est un <form>standard. Il n'y a rien de magique là‑dedans. La « magie » est dans le JavaScript :

// Comme nous voulons avoir accès à un nœud DOM,
// nous initialisons le script au chargement de la page.
window.addEventListener('load', function () {

  // Ces variables s'utilisent pour stocker les données du formulaire
  var text = document.getElementById("i1");
  var file = {
        dom    : document.getElementById("i2"),
        binary : null
      };
 
  // Nous utilisons l'API de FileReader pour accéder au contenu du fichier
  var reader = new FileReader();

  // Comme FileReader est asynchrone, stockons son résulata
  // quand il a fini de lire le fichier
  reader.addEventListener("load", function () {
    file.binary = reader.result;
  });

  // Au chargement de la page, si un fichier est déjà choisi, lisons‑le
  if(file.dom.files[0]) {
    reader.readAsBinaryString(file.dom.files[0]);
  }

  // Sinon, lisons le fichier une fois que l'utilisateur l'a sélectionné
  file.dom.addEventListener("change", function () {
    if(reader.readyState === FileReader.LOADING) {
      reader.abort();
    }
    
    reader.readAsBinaryString(file.dom.files[0]);
  });

  // sendData est notre fonction principale
  function sendData() {
    // S'il y a un fichier sélectionné, attendre sa lecture
    // Sinon, retarder l'exécution de la fonction
    if(!file.binary && file.dom.files.length > 0) {
      setTimeout(sendData, 10);
      return;
    }

    // Pour construire notre requête de données de formulaire en plusieurs parties
    // nous avons besoin d'une instance de XMLHttpRequest
    var XHR = new XMLHttpRequest();

    // Nous avons besoin d'un séparateur pour définir chaque partie de la requête
    var boundary = "blob";

    // Stockons le corps de la requête dans une chaîne littérale
    var data = "";

    // Ainsi, si l'utilisateur a sélectionné un fichier
    if (file.dom.files[0]) {
      // Lancer une nouvelle partie de la requête du corps
      data += "--" + boundary + "\r\n";

      // Décrivons là comme étant des données du formulaire
      data += 'content-disposition: form-data; '
      // Définissons le nom des données du formulaire
            + 'name="'         + file.dom.name          + '"; '
      // Fournissons le vrai nom du fichier
            + 'filename="'     + file.dom.files[0].name + '"\r\n';
      // et le type MIME du fichier
      data += 'Content-Type: ' + file.dom.files[0].type + '\r\n';

      // Il y a une ligne vide entre les métadonnées et les données
      data += '\r\n';
      
      // Ajoutons les données binaires à la requête du corps
      data += file.binary + '\r\n';
    }

    // C'est plus simple pour les données textuelles
    // Démarrons une nouvelle partie dans notre requête du corps
    data += "--" + boundary + "\r\n";

    // Disons que ce sont des données de formulaire et nommons les
    data += 'content-disposition: form-data; name="' + text.name + '"\r\n';
    // Il y a une ligne vide entre les métadonnées et les données
    data += '\r\n';

    // Ajoutons les données textuelles à la requête du corps
    data += text.value + "\r\n";

    // Ceci fait, "fermons" la requête du corps
    data += "--" + boundary + "--";

    // Définissons ce qui arrive en cas de succès pour la soumission des données
    XHR.addEventListener('load', function(event) {
      alert('Ouais ! Données expédiées et réponse chargée.');
    });

    // Définissons ce qui se passe en cas d'eerreur
    XHR.addEventListener('error', function(event) {
      alert('Oups! Quelque chose s\'est mal passé.');
    });

    // Configurons la requête
    XHR.open('POST', 'https://example.com/cors.php');

    // Ajoutons l'en-tête requise pour gèrer la requête POST des données
    // de formulaire en plusieurs parties
    XHR.setRequestHeader('Content-Type','multipart/form-data; boundary=' + boundary);

    // et finalement, expédions les données.
    XHR.send(data);
  }

  // Accéder au formulaire …
  var form = document.getElementById("myForm");

  // … pour prendre en charge l'événement soumission
  form.addEventListener('submit', function (event) {
    event.preventDefault();
    sendData();
  });
});

Voici le résultat en direct :

Conclusion

Selon le navigateur, l'envoi de données de formulaire par JavaScript peut être facile ou difficile. L'objet FormData en est généralement la cause et n'hésitez pas à utiliser un « polyfill » (prothèse d'émulation) pour cela sur les navigateurs anciens :

  • Ces primitives sont des « polyfills » de  FormData avec des worker.
  • HTML5-formdata tente d'opérer un « polyfill » de l'objet FormData, mais il requiert un File API
  • Ce « polyfill » fournit la plupart des nouvelles méthodes dont FormData dispose (entrées, clés, valeurs et prise en charge de for...of)

Dans ce module

Étiquettes et contributeurs liés au document

Contributeurs à cette page : Dralyab
Dernière mise à jour par : Dralyab,