Accueil Blog ⇉ Drag and drop avec previsualisation image

JQUERY

HTML5

illustration_article

Suite à une question posée à propos de l'article sur l'upload et svg, non, il n'est pas compliqué de faire un formulaire avec preview pour les images et encore moins avec un glissé/déposé*, puisque depuis le HTML5 le "drag and drop" est supporté en standard :
Ça ne fait qu'une dizaine d'années que ça existe, je ne sais absolument pas pourquoi on ne s'en sert pas plus (sans doute l'idée que c'est compliqué ?)
* navré pour l'anglicisme, mais "glissé-déposé" ça me fait bizarre, je vais continuer avec "drag and drop"

Il existe des tonnes de plugins plus ou moins bien faits, mais je vous propose de voir comment en faire un (tout petit) en quelques lignes de JQuery.

Chapitre I : le drag and drop

Vidons tout d'abord la question simplissime du drag and drop en quelques mots :
Voici le html(5) pour faire un chouette formulaire :

<form id="svg_f1" enctype="multipart/form-data">
<div id="cont2" style="width:300px;height:70%;">
<div id="dash2" style="margin:5px;height:80%;overflow:hidden;border: dashed 5px lightgrey;">
<input type="file" id="inputImg"
name="svg_file" id="svg_file"
style="opacity:0;position:relative;height:100%;z-index:1" />
</div>
</div>
</form>


Et ce que ça donne :


Si vous cliquez à l'intérieur du rectangle hachuré, ou si vous glissez une image, elle sera prise en compte comme dans n'importe quel input[type=file] à la seule différence qu'on ne le voit pas, grâce à la ligne opacity:0 ; entrez n'importe quel chiffre entre (0 et 1) dans le champ à droite pour voir ce que ça fait.

help “Eh oui, toute la ruse est là.”



Mais, me direz-vous, c'est complètement idiot puisqu'on ne voit rien.
Tout à fait, c'est le premier intérêt du preview (comme son nom l'indique) : voir.

Il faut avant tout, intégrer (et garder à l'esprit) un point important : quand on renseigne un input de type file, on remplit de façon totalement opaque, un objet FileList qui est en lecture seule : en clair, ça signifie qu'on ne peut rien faire d'autre que de le lire sans toutefois pouvoir récupérer les chemins des fichiers directement ; ce sera capital pour la suite. Concrètement, il se présente sous la forme d'un tableau et retourne un objet File accompagné de son index (d'où le File[0] avec un seul fichier)

Chapitre II : le preview simple

Pour ce faire, rajoutons quelques lignes (mais vraiment très peu) à notre formulaire ; en effet, si nous voulons prévisualiser nos images, il faut les mettre quelque part. Deux solutions globales se présentent:
- Créer une image dont on changera l'attribut "src"
- Créer une div dans laquelle on mettra notre image.
Comme nous allons faire un engin un peu plus élaboré, nous allons partir sur la deuxième option, ce qui va conduire à coller quelque part pour le html :

<div id="tt"></div>

Violent, mais il est quand même impératif que #tt soit positionné au-dessous (ou à côté) de l'input, sinon ça ne marchera pas (En fait, l'utilisateur cliquera sur #tt alors qu'il faut cliquer sur le input).

Question Jquery, nous allons utiliser un élément intéressant : L'objet FileReader qui permet à notre petit script de lire dans un fichier (doc ici) grâce à la méthode readAsDataUrl() et qui nous retournera une URL avec les données du fichier en base64 que nous pourrons traiter grâce à l'événement onload.

//détection d'un event sur l'input du formulaire
$("#inputImg").on("change", function(e) {

//création d'un objet FileReader
var reader = new FileReader();

//déclenchement de l'affichage du fichier dans #tt
reader.onload = function (event) {
$("#tt").html("<img src='"+event.target.result+"' style='width:150px;height:100px;margin-left:3px;'/>");
}

//lecture du fichier FileList envoyé par le formulaire
reader.readAsDataURL(e.target.files[0]);

});




Et voilà le travail.
Remarquons que quand on rajoute un fichier, il écrase ce qui est déjà présent, à commencer par le... FileList.

L'honnêteté me force à dire que je n'ai pas la paternité de tout ceci, néanmoins j'ai essayé de l'arranger au plus simple. Si vous n'avez besoin que d'uploader un seul fichier image à chaque fois, vous pouvez vous arrêter là, sinon la suite peut vous intéresser: ce qu'on peut faire avec un fichier, on peut le faire avec plusieurs. Encore une fois, il suffit de quelques modifications.

Chapitre III : le preview avec des images multiples (1)

Avant toutes choses, rajouter dans l'input multiple, et modifier le name du input comme ceci: name="svg_file[]".

pour le Jquery, la procédure est également simple : vous avez sans doute remarqué l'index [0] du FileReader, eh bien, il va juste s'agir de reproduire la même opération à chaque image pour obtenir un preview, pour cela, nous récupérons juste le nombre de fichier dans le FileList et nous créons une boucle for :


var j=0;
$("#inputImg").on("change", function(e) {
var files = e.target.files;

//on boucle sur le nombre de fichiers du FL
for(var i=0; i < files.length; i++) {
var reader = new FileReader();

//on ajoute à la suite chaque nouvelle image renvoyée par readAsDataUrl
reader.onload = function (event) {
$("#tt").append('<img id="_'+j+'" class="prev_img" src="'+event.target.result+'" style="width:150px;height:100px;margin-left:3px;position:relative;z-index:2;"/>');
j++;
}

//on lit chaque nouvelle image de la boucle
reader.readAsDataURL(e.target.files[i]);


}
});


ce qui donne finalement :


Notez que pour cet exemple, le drag and drop permet de récupérer:
- ou bien une image simple (par click ou glisser/déposer)
- ou bien plusieurs images (par click ou drag and drop (glisser/déposer)

Jusqu'à maintenant, la récupération côté serveur est la même que d'habitude

help “Mais... aleeeert !”


Eh oui, vous avez remarqué aussi ? Comme dit plus haut, à chaque nouveau fichier ajouté, on écrase le FileList déjà construit pour repartir avec le nouvel envoi. Si un utilisateur met 10 fichiers d'un coup, et complète avec un seul (même tout petit ;-), il ne restera que le dernier, le tout petit. C'est complètement ingérable, pour ne pas dire nul. C'est la honte définitive ;-(
"Comment? On a fait tout ça pour rien?" vous dites-vous...
Non, rassurez-vous, on va "bypasser" ce problème.

help “Résumons...”


Actuellement, voici où nous en sommes : Nous remplissons facilement via drag and drop ou click standard un élément input file qui construit un FileList et dans lequel nous nous servons pour afficher nos images via notre FileReader. Le problème, c'est que cette méthode n'accepte aucune modification (hormis l'"écrasade" sauvage) et que même si nous supprimons des previews de notre FileReader ça ne servira strictement à rien, puisqu'elles resteront dans le FileList ; nous allons donc utiliser autre chose en parallèle :
L'objet FormData. Pourquoi en parallèle ? parce qu'à ma connaissance, on ne peut pas l'utiliser pour récupérer le chemin des fichiers (pas plus que FileList).
Le FileList va donc nous servir pour obtenir les previews via le FileReader et le FormData enverra les données au serveur à la place du... FileList !

Il présente beaucoup d'avantages qui nous intéressent, d'abord il est "iterable" comme disent les Anglais, ce qui signifie qu'on peut se promener dedans, et qu'on peut y mettre ce qu'on veut, et en enlever ce qu'on veut aussi. Voici les deux méthodes que nous allons utiliser:
FormData.append("index", "name"); et FormData.delete("index");

A noter, on peut utiliser formData de façon plus simple (et plus radicale) pour récupérer l'ensemble des valeurs d'un formulaire, imaginons par exemple un formulaire avec une id id="myForm", ça donnera quelque chose comme :

let form = document.getElementById("myForm");
let xhr = new XMLHttpRequest();
let formDat = new FormData(form);
//...
xhr.send(formDat);



Chapitre III : le preview avec des images multiples (2)

Commençons :
Note I : La manière de faire ci-dessous est une parmi beaucoup d'autres, avec ses avantages et ses inconvénients, elle n'est absolument pas académique de fait.

Note II : Le formulaire HTML ne change pas (la bonne nouvelle)

Reprenons le script JQ du dessus ; l'étape 1 est de "lier" le [FileList] au [FormData] pour savoir à qui nous avons affaire (en terme d'image), ce sera utile pour détruire celles dont on ne veut plus ; ça donne :

var j=0; // compteur du FileList
var z = 0;// indexes du FormData
var formDat = new FormData();// création de notre objet FormData



Et le point important :

for(var i=0; i < files.length; i++) { // A chaque fichier...
formDat.append(z++, files[i]);// on rentre dans le FomData son index (la même valeur que l'ID donnée à la preview correspondante et le nom du fichier


C'est tout ? Oui, le reste ne change pas, c'est donc tout, du moins pour l'ajout.
Maintenant que nous sommes prêts pour faire un truc complet, pourquoi ne pas donner la possibilité de retirer une image ?
Rappelons-nous, pour chaque preview affichée, nous avons une classe prev_img, une id sous la forme _X qui correspond à l'index de ladite image dans le FormData, il ne nous reste plus qu'à faire :

$(document).on("click", ".prev_img", function(){ // en cas de click sur une image preview :
var delId = $(this).prop('id').substr(1); //on récupère l'id sans le _
formDat.delete(delId); // on la détruit dans le formdata
$(this).remove(); //on la détruit dans l'affichage des images preview
});


Et nous obtenons un magnifique upload complet (j'ai ajouté un input text et en-dessous, le FormData en "live" pour bien voir comment il se présente)


Note 3 :il est brut, absolument pas protégé, donc il ne faudra surtout pas oublier de mettre les verrous habituels (par sécurité déjà, et sous peine de dysfonctionnement ensuite).

Votre formulaire est fini.

Mais si vous avez bien suivi, dans un coin de votre tête il y a certainement une petite voix qui vous dit "Mais ce type est complètement nul, le FileList n'a pas changé d'un iota !"

C'est exact (et c'est prévu ouf), pour la dernière étape (fastoche rassurez-vous) nous allons passer directement à la partie Ajax :

Chapitre IV et fin

L'opération finale consiste à passer au data d'Ajax, le FormData en argument, c'est très simple et ça se fait de cette façon :

formDat.delete("inptest"); // on remet à zéro les données qui pourraient être déjà rentrées, en cas de re-soumission
formDat.append("inptest",$("#inpTest").val()); //on ajoute le nom de l'input text et sa valeur
processData: false, // empêche notre Formdata d'être converti en string
contentType: false, // empêche l'application du CT UTF-8 (défaut)
data:formDat, //ajoute (enfin) le FormData à l'envoi



Le bouton "valider" sert à simuler un envoi Ajax où vous pouvez vérifier ce qui est bien envoyé (y compris le "inptest") dans l'onglet "Network" de la console.

Note côté serveur

Vu qu'il n'y a plus de nom de quoi que ce soit et des index qui changent à chaque fichier, il ne faut pas espérer grand-chose de $_FILES['svg_file']['tmp_name'] qui va lever un superbe "Undefined index: svg_file", il suffit de boucler $_FILES avec for ou foreach pour régler cette question sans aucun problème, en gardant toutefois à l'esprit que supprimer une image preview créera un "trou" dans le FormData, par exemple si on supprime la 3è image, ça donnera quelque chose comme:


0, 7_19_30.jpg
1, 11_11_15.jpg
3, 3_37_45.jpg
4, 5_29_40.jpg
5, 6 18 31.jpg


Avec foreach ça ne posera pas de problème, mais une boucle for lèvera une erreur sur le [2] qu'il ne trouvera pas ; dans ce cas, n'oubliez pas de reconstruire votre tableau d'images de cette façon par exemple :

$_FILES = array_values($_FILES);



J'ai essayé d'être le plus clair possible et comme précisé plus haut, ce n'est pas la seule façon de faire ni forcément la meilleure mais elle a le mérite de marcher de façon satisfaisante en séparant bien les étapes. Comme d'habitude, une remarque ou une question, n'hésitez pas à me contacter !



Modifié le 23-08-2020
Article précédent


JQuery : Progress bar animée et SVG circle pendant un upload
Article suivant


HTML : Aligner le dernier élément d'une liste

Commentaires

 Fidele  2021-05-21

Merci pour le tuto. C'est bien toujours de savoir faire soi-même au lieu de s'attacher à un plugin

 Benji  2023-03-05

Très sympa ce tuto, merci !!

Publier un commentaire :



capcha   



Raccourcis : php css html sql js img

Prévisualiser
GB-Net.fr 2024



fleche haut
Résultats de votre recherche pour :
reduce close