Upload via une iframe

L'AJAX ne se limite pas à l'utilisation de l'objetXMLHttpRequest, il existe bien d'autres manières de communiquer avec un serveur. La balise<iframe>fait partie des diverses autres solutions possibles.

Vous avez probablement déjà entendu parler de cette balise et, comme beaucoup de monde, vous pensez probablement qu'elle est à éviter. Disons que, dans l'ensemble, oui, mais il existe certains cas où elle devient rudement efficace, notamment pour l'upload de fichiers !

Manipulation des iframes

Les iframes

Peut-être connaissez-vous l'élément HTML<iframe>? Pour ceux qui ne le connaissent pas, c'est un élément qui permet d'insérer une page Web dans une autre. Voici un petit rappel de la syntaxe d'une iframe :

<iframe src="file.html" name="myFrame" id="myFrame"></iframe>

Accéder au contenu

Pour accéder au contenu de l'iframe, il faut d'abord accéder à l'iframe elle-même et ensuite passer par la propriétécontentDocument:

var frame = document.getElementById('myFrame').contentDocument

Une fois que l'on a accédé au contenu de l'iframe, c'est-à-dire à son document, on peut naviguer dans le DOM comme s'il s'agissait d'un document « normal » :

var frame_links = frame.getElementsByTagName('a').length;

Chargement de contenu

Il y a deux techniques pour charger une page dans une iframe. La première est de tout simplement changer l’attributsrcde l'iframe via le JavaScript, la deuxième est d'ouvrir un lien dans l'iframe. Cette action est rendue possible via l'attributtarget(standardisé en HTML5) que l'on peut utiliser sur un lien ou sur un formulaire. C'est cette dernière technique que nous utiliserons pour la réalisation du système d'upload.

Charger une iframe

En changeant l'URL

Ici, rien de compliqué, on change simplement l'URL de l'iframe en changeant sa propriétésrc. Cette technique est simple et permet de transmettre des paramètres directement dans l'URL. Exemple :

document.getElementById('myFrame').src = 'request.php?nick=Thunderseb';
Avectargetet un formulaire

L'intérêt d'utiliser un formulaire est que nous allons pouvoir envoyer des données via la méthode POST. L'utilisation de POST va nous permettre d'envoyer des fichiers, ce qui nous sera utile pour un upload de fichiers !

En fait, pour cette technique, il n'y a pas vraiment besoin du JavaScript, c'est du HTML pur :

<form id="myForm" method="post" action="request.php" target="myFrame">
<div>
<!-- formulaire -->
<input type="submit" value="Envoyer" />
</div>
</form>
<iframe src="#" name="myFrame" id="myFrame"></iframe>

L'attributtargetindique au formulaire que son contenu doit être envoyé au sein de l'iframe dont l'attributnameestmyFrame(l'attributnameest donc obligatoire ici !). De cette manière le contenu du formulaire y sera envoyé, et la page courante ne sera pas rechargée.

Le JavaScript pourra être utilisé comme méthode alternative pour envoyer le formulaire. Pour rappel, pour envoyer un formulaire, il faut utiliser la méthodesubmit():

document.getElementById('myForm').submit();

Détecter le chargement

Avec l'événementload

Les iframes possèdent un événementload, déclenché une fois que le contenu de l'iframe est chargé. À chaque contenu chargé,loadest déclenché. C'est un moyen efficace pour savoir si le document est chargé, et ainsi pouvoir le récupérer. Voici un petit exemple :

<iframe src="file.html" name="myFrame" id="myFrame" onload="trigger()"></iframe>
<script>
function trigger() {
var frame = document.getElementById('myFrame').contentDocument;
alert(frame.body.textContent);
}
</script>
Avec une fonction de callback

Quand une page Web est chargée dans l'iframe, son contenu est affiché et les scripts sont exécutés. Il est également possible, depuis l'iframe, d'appeler une fonction présente dans la page « mère », c'est-à-dire la page qui contient l'iframe.

Pour appeler une fonction depuis l'iframe, il suffit d'utiliser :

window.top.window.nomDeLaFonction();

L'objetwindow.toppointe vers la fenêtre « mère », ce qui nous permet ici d'atteindre la page qui contient l'iframe.

Voici un exemple qui illustre ce mécanisme :

<iframe src="file.html" name="myFrame" id="myFrame"></iframe>
<script>
function trigger() {
var frame = document.getElementById('myFrame').contentDocument;
alert('Page chargée !');
}
</script>
<script>
window.top.window.trigger(); // On appelle ici notre fonction de callback
</script>
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Suspendisse molestie suscipit arcu.</p>

Essayer le code

Récupérer du contenu

Le chargement de données via une iframe a un gros avantage : il est possible de charger n'importe quoi comme données. Ça peut être une page Web complète, du texte brut ou même du JavaScript, comme le format JSON.

Récupérer des données JavaScript

Si on reprend l'exemple vu précédemment, avec le callback, il est possible de récupérer facilement des données JavaScript, comme un objet. Dans ce cas, il suffit d'utiliser du PHP pour construire un objet qui sera transmis en paramètre de la fonction de callback, comme ceci :

<?php
$fakeArray = array('Sébastien', 'Laurence', 'Ludovic');
?>
<script>
window.top.window.trigger(['<?php echo implode("','", $fakeArray) ?>']);
</script>

Ici, un tableau JavaScript est construit via le PHP et envoyé à la fonctiontrigger()en tant que paramètre.

Exemple complet

<form id="myForm" method="post" action="request.php" target="myFrame">
<div>
<label for="nick">Votre pseudo :</label>
<input type="text" id="nick" name="nick" />
<input type="button" value="Envoyer" onclick="sendForm();" />
</div>
</form>
<iframe src="#" name="myFrame" id="myFrame"></iframe>
<script>
function sendForm() {
var nick = document.getElementById("nick").value;
if (nick) { // Si c'est OK
document.getElementById("myForm").submit(); // On envoie le formulaire
}
}
function receiveData(data) {
alert('Votre pseudo est "' + data + '"');
}
</script>

Et maintenant la page PHP :

<script>
window.top.window.receiveData("<?php echo htmlentities($_POST['nick']); ?>");
</script>

Essayer le code

Ce script ne fait que récupérer la variable$_POST['nick'], pour ensuite appeler la fonctionreceiveData()en lui passant le pseudo en paramètre. La fonction PHP htmlentities()
permet d'éviter que l'utilisateur insère d'éventuelles balises HTML potentiellement dangereuses telles que la balise<script>. Alors, certes, ici l'insertion de balise ne pose pas de problème puisque l'on affiche le pseudo dans une fenêtrealert(), mais mieux vaut prévenir que guérir, non ?

Le système d'upload

Par le biais d'un formulaire et d'une iframe, créer un système d'upload n'est absolument pas compliqué. C'est même relativement simple ! Les éléments<form>possèdent un attributenctypequi doit absolument contenir la valeurmultipart/form-data. Pour faire simple, cette valeur indique que le formulaire est prévu pour envoyer de grandes quantités de données (les fichiers sont des données volumineuses).

Notre formulaire d'upload peut donc être écrit comme ceci :

<form id="uploadForm" enctype="multipart/form-data" action="upload.php" target="uploadFrame" method="post">
<label for="uploadFile">Image :</label>
<input id="uploadFile" name="uploadFile" type="file" />
<br /><br />
<input id="uploadSubmit" type="submit" value="Upload !" />
</form>

Ensuite, on place l'iframe, ainsi qu'un autre petit<div>que nous utiliserons pour afficher le résultat de l'upload :

<div id="uploadInfos">
<div id="uploadStatus">Aucun upload en cours</div>
<iframe id="uploadFrame" name="uploadFrame"></iframe>
</div>

Et pour finir, une dose de JavaScript :

function uploadEnd(error, path) {
if (error === 'OK') {
document.getElementById('uploadStatus').innerHTML = '<a href="' + path + '">Upload done !</a><br /><br /><a href="' + path + '"><img src="' + path + '" /></a>';
} else {
document.getElementById('uploadStatus').innerHTML = error;
}
}
document.getElementById('uploadForm').addEventListener('submit', function() {
document.getElementById('uploadStatus').innerHTML = 'Loading...';
});

Quelques explications s'imposent. Dès que le formulaire est envoyé, la fonction anonyme de l'événementsubmitest exécutée. Celle-ci va remplacer le texte du<div>#uploadStatuspour indiquer que le chargement est en cours. Car, en fonction de la taille du fichier à envoyer, l'attente peut être longue. L'argumenterrorcontiendra soit « OK », soit une explication sur une erreur éventuelle. L'argumentpathcontiendra l'URL du fichier venant d'être uploadé. L'appel vers la fonctionuploadEnd()sera fait via l'iframe, comme nous le verrons plus loin.

Le code côté serveur : upload.php

Le JavaScript étant mis en place, il ne reste plus qu'à nous occuper de la pageupload.phpqui va réceptionner le fichier uploadé. Il s'agit d'un simple script d'upload :

<?php
$error = NULL;
$filename = NULL;
if (isset($_FILES['uploadFile']) && $_FILES['uploadFile']['error'] === 0) {
$filename = $_FILES['uploadFile']['name'];
$targetpath = getcwd() . '/' . $filename; // On stocke le chemin où enregistrer le fichier
// On déplace le fichier depuis le répertoire temporaire vers $targetpath
if (@move_uploaded_file($_FILES['uploadFile']['tmp_name'], $targetpath)) { // Si ça fonctionne
$error = 'OK';
} else { // Si ça ne fonctionne pas
$error = "Échec de l'enregistrement !";
}
} else {
$error = 'Aucun fichier réceptionné !';
}
// Et pour finir, on écrit l'appel vers la fonction uploadEnd :
?>
<script>
window.top.window.uploadEnd("<?php echo $error; ?>", "<?php echo $filename; ?>");
</script>

Avec ce script tout simple, il est donc possible de mettre en place un upload de fichiers sans « rechargement ». Il ne reste plus qu'à améliorer le système, notamment en sécurisant le script PHP (détecter le type MIME du fichier, pour n'autoriser que les images par exemple), ou en arrangeant le code JavaScript pour afficher à la suite les fichiers uploadés s'il y en a plusieurs…

Si vous souhaitez essayer ce script en ligne, sachez que nous avons mis une version en ligne, mais que celle-ci n'enregistre pas les fichiers sur le serveur et que cela implique donc que l'affichage de l'image n'est pas effectué. Vous êtes en revanche prévenus lorsqu'un fichier a fini d'être uploadé, ce qui est, somme toute, le but principal de notre script.

Essayer la version « light » !

En résumé
  • L'utilisation d'une iframe est une technique AJAX assez répandue et facile à mettre en œuvre pour réaliser un upload de fichiers compatible avec tous les navigateurs.

  • Il suffit d'utiliser l'événementloadsur une iframe pour savoir si la page qu'elle contient vient d'être chargée. Il ne reste plus qu'à accéder à cette page et à récupérer ce qui nous intéresse.

  • Depuis une iframe, il faut utiliserwindow.toppour accéder à la page qui contient l'iframe. C'est utile dans le cas d'un callback.