Asynchronous Javascript And XML

Exemple GET

<!--HTML-->
<div id="a">Texte par défaut</div>
<a href="#" onclick="return test();">tester</a>

<!--Javascript-->
<script>
// Envoie une requête HTTP au serveur web et récupère sa réponse,
// met à jour localement le DOM sans charger une nouvelle page.
function test(){

    // Le mot-clé new crée une nouvelle instance de la classe XMLHttpRequest
    const xhr = new XMLHttpRequest();
    // on peut préciser le script ou une URL à la place
    xhr.open("GET", "ajax.php", true);
    xhr.send();

    // Callback qui sera invoqué plus tard par Javascript 
    xhr.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200){
            //alert ('200 OK chaîne reçue:' + this.responseText);
            // this fait référence à l'objet qui utilise ce terme, ici xhr
            document.getElementById("a").innerHTML = this.responseText;
         }
     };
    return false;
}

// F12 onglet Network: on voit la requête HTTP qui part
//vers le serveur avec le ping. XHR = XMLHTTPRequest

// décommenter pour l'exécuter direct au démarrage
//test();
</script>

<!-- Le script PHP -->
<?php
echo 'Ajax1 ok';
?>
Points clés à retenir

AJAX s'appuie sur la création et manipulation d'une instance de la classe XMLHTTPRequest.

Synchrone ou Asynchrone? (bloquant ou non bloquant?)

Si open() est sur false (donc sync) alors send() est bloquante: send() bloque l'exécution du code JS jusqu'à ce que le fichier soit reçu.
En async le téléchargement se fait en arrière-plan, le callback onreadystatechange() n'est invoqué plus tard que lorsque le fichier est reçu.

->[en mode sync si vous mettez une instruction ligne 14 elle sera exécutée après que le fichier a été complètement reçu]
->[en mode async si vous mettez une instruction ligne 14 elle sera exécutée immédiatement, mais le fichier arrivera ultérieurement]

Synchrone: le code demande un fichier au serveur, le fichier est reçu, puis le code continue son exécution.
Asynchrone: le code demande un fichier au serveur, le code continue son exécution et le fichier arrive plus tard on ne sait pas quand.

Dans open() async vaut true par défaut.

Exemple GET
avec passage de paramètres

Code HTML/Javascript
<div id="texte">texte par défaut</div>

<script>
function test2(ville)
{
    const xhr = new XMLHttpRequest();
        xhr.open("GET", "ajax2.php?ville="+ville);
        xhr.send();

    xhr.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200){
            document.getElementById('texte').innerHTML = this.responseText;
        }
    };
    return false;
}
</script>

<a href="#" onclick="return test2('Strasbourg');">tester</a>
Le script PHP
<?php
$a = $_GET['ville'];
echo $a;

// Écrit la chaîne dans le fichier
//file_put_contents('test.txt', $a);

// $_GET['ville'] contient un nom de ville, ou autre chose...
// Attention Sécurité Ne JAMAIS faire confiance au client, on ne sait jamais quelle
//espèce de string ils vont envoyer pour tenter une injection SQL!
// PHP6 a apporté une nouvelle fonction pour "désinfecter" une chaîne GET ou POST
//provenant du réseau:

//Commenter les lignes 2 et 3 puis décommenter ici:
/*
$str = filter_input(INPUT_GET, 'ville', FILTER_SANITIZE_STRING);
file_put_contents('test.txt', $str);
echo 'ok';
*/
?>
En AJAX le serveur web envoie un string.
Ce string peut contenir:
- 1 valeur (ici la ville) un mot ou une phrase
- des valeurs séparées par un séparateur ; (un fichier CSV est une table)
- un contenu XML
- ou mieux un fichier JSON (objet qui ressemble à un tableau Javascript, au format string pour le faire transiter par le réseau)

GET

Le serveur envoie une série de noms séparés par point-virgules.
Le client remplit une liste avec.
<script>
function get(str)
{
    // vide la liste
    document.getElementById("liste").innerHTML = "";

    var xhr = new XMLHttpRequest();
    xhr.open("GET", "ajax.php?secteur=" + str, true);
    xhr.send();

    xhr.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
            //document.getElementById("a").innerHTML = this.responseText;

            var s = this.responseText.split(";");
            for(n=0; n<s.length; n++)
            {
                if(!s[n]) continue;
                // ajoute une option à la liste
                var opt = document.createElement('option');
                opt.value = s[n];
                opt.innerHTML = s[n];
                document.getElementById('liste').appendChild(opt);
            }
        }
    };
}
</script> <span id="a"></span> <select id="liste"> <option value="">Villes</option> </select> <button onclick="get('D');">Secteur D</button> <button onclick="get('E');">Secteur E</button>
<?php
$mysqli = mysqli_connect("localhost", "user", "passe", "base") ;

// sort les villes du secteur D
$secteur = addslashes($_GET['secteur']);
$sql = "SELECT DISTINCT(ville)
			FROM sudwall
			WHERE secteur = '$secteur'
			ORDER BY ville;";
$r = mysqli_query($mysqli, $sql);
while($u = mysqli_fetch_array($r))
{
	//echo '<option value="'.u[6].'">'.u[1].'</option>'."\n";
	echo $u[0].";";
}

mysqli_close($mysqli);
?>





Exemple POST

Le client envoie deux champs texte, avec la méthode POST. La particularité de la méthode POST:
<script>
function onSubmit()
{
    // pour ne pas valider sans avoir oublié un champ
    var a = document.frm;
    if(a.X.value =='')     {alert('sans X?');    a.X.focus(); return false; }
    if(a.Y.value =='')    {alert('sans Y?');    a.Y.focus(); return false; }

    const xhr = new XMLHttpRequest();
    document.getElementById('info').innerHTML = '';

    xhr.open("POST", "ajax.php", true);
    xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    xhr.send('X='+a.X.value+'&Y='+a.Y.value);
    xhr.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200)
        {
            var str = this.responseText;
            document.getElementById('info').innerHTML = str;
        }
    };

    return false;
}
</script> <span id="info"></span> <form name="frm"> <input type="text" name="X" value="44.032222"/> <input type="text" name="Y" value="5.5566611"/> </form> <a href="#" onclick="return onSubmit();">Valider</a>
<?php
echo "X:{$_POST['X']} Y:{$_POST['Y']}";
?>



Upload images multiple

Pour soumettre un formulaire avec la méthode POST le client crée une instance d'objet FormData.
Notez qu'il transfère aussi un champ caché qui est lui aussi ajouté au formData avec sa méthode append().

Le serveur fait quelques vérifications mais devrait aussi vérifier le type MIME.
<script>
function upload()
{
    const xhr;

    try {
        xhr = new XMLHttpRequest();
    } catch (e) {
        xhr = new ActiveXObject("Microsoft.XMLHTTP");
    }

    xhr.onreadystatechange = function() {
        if ((this.readyState == 4) && (this.status == 200)) {  
            document.getElementById('info').innerHTML = this.responseText;
        }
    };

    document.getElementById('info').innerHTML = '';
    const files = document.querySelector('[type=file]').files;
    const formData = new FormData();

    for (let i=0; i < files.length; i++) {
        let file = files[i];
        formData.append('files[]', file);
    }

    formData.append('dos', document.getElementById('dos').value);

    xhr.open("POST", "upload.php", true);
    xhr.send(formData);
}
</script>

<form method="post" enctype="multipart/form-data"> <input type="hidden" name="dos" id="dos" value="Batterie du Pharo"/> <input type="file" name="files[]" onchange="upload();" multiple /> </form> <span id="info"></span>
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (isset($_FILES['files'])) {
        //print_r(_FILES);
        echo 'dossier:'.$_POST['dos'].'<br/>';

        $nb = count($_FILES['files']['tmp_name']);
        $extensions = ['jpg', 'jpeg', 'png', 'gif'];

        for ($i=0; $i < $nb; $i++)
        {
            $file_name = $_FILES['files']['name'][$i];
            $file_tmp  = $_FILES['files']['tmp_name'][$i];
            $file_type = $_FILES['files']['type'][$i];
            $file_size = $_FILES['files']['size'][$i];

            $file_ext  = strtolower(end(explode('.', $file_name)));

            if (!in_array($file_ext, $extensions))
                echo $file_name.' Desole jpg jpeg png et gif seulement<br/>';

            else if ($file_size > 2097152)
               echo $file_name.' taille maxi dépassée<br/>';
            else
            {
              move_uploaded_file($file_tmp, 'up/'.$file_name);
              echo $_POST['dos'].'/'.$file_name.' ok<br/>';
            }
        }
    }
}
?>


Personnalisation de l'attente

Pendant que la requête est envoyée, signaler à l'utilisateur que la requête est en cours de traitement. Ceci est faisable en donnant temporairement au body une classe d'attente:
<style>
/* en attendant la réponse AJAX body prend cette classe */
.loading{
	position:fixed;
	height:100%;
	width:100%;
	left:0;
	top:0;
	cursor:wait;
	background:#000;
	opacity:.5;
	z-index:999
}
</style>
<script>
function ajax(var)
{
    // met l'arrière plan en foncé, et le curseur qui tourne
    document.body.className = 'loading';

    const xhr = new XMLHttpRequest();
    xhr.open("GET", "robin/ajax.php?var="+var, true);
    xhr.send();
    xhr.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
            document.getElementById("a") = this.responseText;

            // traitement terminé: repasse en mode normal
            document.body.className = '';
        }
    };
    return false;
}
</script>

Obtenir le contenu d'un fichier


// contenu de foo.txt
let text = `My test text goes here!`;

<script src="foo.txt"></script>
<script>
  console.log(text);
</script>

<script>
// en AJAX
const xhr = new XMLHttpRequest();
xhr.open('GET', '/foo.txt');
xhr.onreadystatechange = function() {
    alert(client.responseText);
}
xhr.send();

// avec fetch
fetch('http://localhost/foo.txt')
    .then(response => response.text())
    .then((data) => {
        console.log(data)
    })

// avec fetch await
const response = await fetch('http://localhost/foo.txt');
const data = await response.text();
console.log(data);
</script>

Obtenir un script JS et l'exécuter

<script>
// un dossier contient plusieurs fichiers js qui
// contiennent le même tableau mais avec des valeurs différentes.

//décharge le script avant de charger le suivant
const scriptElem = document.getElementById('SOME_ID');
if(scriptElem) scriptElem.remove();

let script = document.createElement("script");
    script.type = "text/javascript";
    script.src = 'data/'+projet+'.js?cache='+(Math.random()*1000000);
    script.id = 'SOME_ID';
document.body.appendChild(script);

script.onload = function() {
    // ici code exécuté quand le script est chargé,
    // on peut invoquer le code qui est dans le script
    alert(t[0][0]);
};
</script>