Travailler avec le stockage de données de glissement
L'interface DragEvent possède une propriété dataTransfer, qui est un objet DataTransfer. Les objets DataTransfer représentent le contexte principal de l'opération de glissement, et restent cohérents tout au long du déclenchement des différents évènements. Ils incluent les données de glissement, l'image de glissement, l'effet de dépôt, etc. Cet article se concentre sur la partie stockage des données du dataTransfer.
Structure du stockage des données déplacées
Fondamentalement, le stockage des données de glissement est une liste d'éléments, représentée comme un DataTransferItemList d'objets DataTransferItem. Chaque élément peut être de l'un des deux types :
string: sa charge utile est une chaîne de caractères, récupérable avecgetAsString().file: sa charge utile est un objet de fichier, récupérable avecgetAsFile()(ougetAsFileSystemHandle()ouwebkitGetAsEntry(), si des opérations plus complexes sur le système de fichiers sont nécessaires).
De plus, l'élément est également identifié par un type, qui par convention est sous la forme d'un type MIME. Ce type peut indiquer au consommateur comment la charge utile doit être analysée ou décodée. Pour tous les éléments de texte, la liste ne peut contenir qu'un seul élément de chaque type, donc la liste contient en effet deux collections disjointes : une liste de fichiers avec des types potentiellement dupliqués, et une Map d'éléments de texte indexés par leur type. En général, la liste de fichiers représente plusieurs fichiers en cours de glissement. La carte de texte ne représente pas plusieurs ressources transférées, mais la même ressource encodée de différentes manières, afin que le destinataire puisse choisir l'interprétation la plus appropriée et prise en charge. Les éléments de texte sont destinés à être triés par ordre décroissant de préférence.
Cette liste est accessible par la propriété DataTransfer.items.
L'API HTML Glisser-déposer a traversé plusieurs itérations, ce qui a entraîné deux façons coexistantes de gérer le stockage des données. Avant les interfaces DataTransferItemList et DataTransferItem, « l'ancienne méthode » utilisait les propriétés suivantes sur DataTransfer :
types: contient les propriétéstypedes éléments de texte dans la liste, plus la valeur"files"s'il y a des éléments de fichier.setData(),getData(),clearData(): fournissent un accès aux éléments de texte dans la liste en utilisant le modèle de « cartographie type-vers-charge utile ».files: fournit un accès aux éléments de fichier dans la liste en tant queFileList.
Vous pouvez constater que les types des éléments de fichier ne sont pas directement exposés. Ils sont toujours accessibles, mais uniquement par la propriété type de chaque objet File dans la liste files, donc si vous ne pouvez pas lire les fichiers, vous ne pouvez pas non plus connaître leurs types (voir lecture du stockage des données de glissement pour savoir quand le stockage est lisible).
Pour obtenir les fichiers et leurs types, nous recommandons d'utiliser la propriété items car elle fournit une interface plus flexible et cohérente. Pour les éléments de texte, vous devriez également préférer l'utilisation de la propriété items pour la cohérence, bien que la méthode getData() soit plus pratique pour accéder ou supprimer un type spécifique.
Une autre différence clé entre les interfaces DataTransfer et DataTransferItem est que la première utilise la méthode synchrone getData() pour accéder à la charge utile de texte, tandis que la seconde utilise la méthode asynchrone getAsString().
Modifier le stockage des données de glissement
Pour les éléments déplaçables par défaut tels que les images, les liens et les sélections, les données de glissement sont déjà définies par le navigateur ; pour les éléments déplaçables personnalisés définis à l'aide de l'attribut draggable, vous devez définir vous-même les données de glissement. Le seul moment pour apporter des modifications au stockage des données est dans le gestionnaire dragstart — pour le dataTransfer de tout autre évènement de glissement, le stockage des données est non modifiable.
Pour ajouter des données de texte au stockage des données de glissement, la « nouvelle méthode » utilise la méthode DataTransferItemList.add(), tandis que « l'ancienne méthode » utilise la méthode DataTransfer.setData().
function dragstartHandler(ev) {
// Nouvelle méthode : add(data, type)
ev.dataTransfer.items.add(ev.target.innerText, "text/plain");
// Ancienne méthode : setData(type, data)
ev.dataTransfer.setData("text/html", ev.target.outerHTML);
}
const p1 = document.getElementById("p1");
p1.addEventListener("dragstart", dragstartHandler);
Pour les deux méthodes, si elles sont appelées lorsque le stockage des données est non modifiable, rien ne se passe. Si un élément de texte du même type existe déjà, add() génère une erreur tandis que setData() remplace l'élément existant.
Pour ajouter des données de fichier au stockage des données de glissement, la « nouvelle méthode » utilise toujours la méthode DataTransferItemList.add(). Comme « l'ancienne méthode » stocke les éléments de fichier dans la propriété DataTransfer.files, qui est une FileList en lecture seule, il n'y a pas d'équivalent direct.
function dragstartHandler(ev) {
// Nouvelle méthode : add(data)
ev.dataTransfer.items.add(new File([blob], "image.png"));
}
const p1 = document.getElementById("p1");
p1.addEventListener("dragstart", dragstartHandler);
On notera que lorsque l'on ajoute des données de fichier, add() ignore le paramètre type et utilise la propriété type de l'objet File.
Note :
La protection de lecture/écriture est effectuée sur une base par tâche, ce qui signifie que seul le code synchrone à l'intérieur du gestionnaire dragstart peut modifier le stockage des données. Si vous essayez d'accéder au stockage des données après une opération asynchrone, vous n'aurez plus les permissions d'écriture. Par exemple, ceci ne fonctionne pas :
function dragstartHandler(ev) {
canvas.toBlob((blob) => {
ev.dataTransfer.items.add(new File([blob], "image.png"));
});
}
La suppression des données est similaire, en utilisant les méthodes DataTransferItemList.remove(), DataTransferItemList.clear() ou DataTransfer.clearData().
Lire le stockage des données de glissement
Le seul moment où vous pouvez lire à partir du stockage des données, en dehors de l'évènement dragstart lorsque vous avez un accès complet au stockage des données, est lors de l'évènement drop, permettant à la cible de dépôt de récupérer les données.
Pour lire les données textuelles à partir du stockage des données de glissement, la « nouvelle méthode » utilise l'objet DataTransferItemList, tandis que « l'ancienne méthode » utilise la méthode DataTransfer.getData(). La nouvelle méthode est plus pratique pour parcourir tous les éléments, tandis que l'ancienne méthode est plus pratique pour accéder à un type spécifique.
function dropHandler(ev) {
// Nouvelle méthode : parcourir les éléments
for (const item of ev.dataTransfer.items) {
if (item.kind === "string") {
item.getAsString((data) => {
// Faire quelque chose avec les données
});
}
}
// Ancienne méthode : getData(type)
const data = ev.dataTransfer.getData("text/plain");
}
const p1 = document.getElementById("p1");
p1.addEventListener("drop", dropHandler);
Pour lire les données de fichier à partir du stockage des données de glissement, la « nouvelle méthode » utilise toujours l'objet DataTransferItemList, tandis que « l'ancienne méthode » utilise la propriété DataTransfer.files.
function dropHandler(ev) {
// Nouvelle méthode : parcourir les éléments
for (const item of ev.dataTransfer.items) {
if (item.kind === "file") {
const file = item.getAsFile(); // Un objet File
}
}
// Ancienne méthode : parcourir les fichiers
for (const file of ev.dataTransfer.files) {
// Faire quelque chose avec le fichier
}
}
const p1 = document.getElementById("p1");
p1.addEventListener("drop", dropHandler);
Mode protégé
En dehors des évènements dragstart et drop, le stockage des données est en mode protégé, empêchant le code d'accéder à toute charge utile. Notamment :
- Toutes les tentatives de modification ne font rien silencieusement ou lèvent une
DOMException(uniquement pouritems.add()etitems.remove()). DataTransfer.getData()retourne toujours une chaîne de caractères vide.DataTransfer.filesretourne toujours une liste vide.DataTransferItem.getAsString()ne fait jamais appel à la fonction de rappel.DataTransferItem.getAsFile()retourne toujoursnull.
Encore une fois, la protection en lecture/écriture est effectuée sur une base par tâche, ce qui signifie que seul le code synchrone à l'intérieur du gestionnaire drop peut lire le stockage des données. Si vous essayez d'accéder au stockage des données après une opération asynchrone, vous n'aurez plus les permissions d'écriture. Par exemple, cela ne fonctionne pas :
function getDataPromise(item) {
return new Promise((resolve) => {
item.getAsString((data) => {
resolve(data);
});
});
}
async function dropHandler(ev) {
for (const item of ev.dataTransfer.items) {
if (item.kind === "string") {
// Mauvais : la deuxième fois que cela s'exécute, nous ne sommes plus dans la même tâche
const data = await getDataPromise(item);
}
}
}
const p1 = document.getElementById("p1");
p1.addEventListener("drop", dropHandler);
Au lieu de cela, vous devez appeler toutes les méthodes d'accès de manière synchrone dès le départ, et attendre leurs résultats plus tard :
async function dropHandler(ev) {
const promises = [];
for (const item of ev.dataTransfer.items) {
if (item.kind === "string") {
// Mauvais : la deuxième fois que cela s'exécute, nous ne sommes plus dans la même tâche
promises.push(getDataPromise(item));
}
}
const results = await Promise.all(promises);
}
Les types de données de glissement communes
La spécification ne définit le comportement que pour quelques types de données, mais les navigateurs prennent parfois en charge nativement d'autres types. En général, les types sont destinés à être un protocole tout comme les types MIME, et vous pouvez utiliser n'importe quel type tant que le destinataire (une autre page web, une autre partie de la même page web, ou même quelque part en dehors du navigateur) le comprend. Cette section décrit certaines conventions courantes et les comportements par défaut des navigateurs.
Notez que les scénarios ci-dessous se réfèrent à l'intention et non au comportement. Par exemple, lorsque nous disons « faire glisser un lien », l'utilisateur·ice peut ne pas faire glisser un élément <a> réel ; il peut faire glisser un conteneur contenant un ou plusieurs liens, mais l'intention est de transférer le(s) lien(s) en tant que données, donc le stockage des données que vous préparez peut être le même que si l'utilisateur·ice faisait glisser un lien réel.
Glisser du texte
Pour faire glisser du texte, utilisez le type text/plain, avec la chaîne de caractères à faire glisser comme valeur. Par exemple :
event.dataTransfer.items.add("This is text to drag", "text/plain");
Vous devez toujours ajouter des données de type text/plain comme solution de repli pour les applications ou les cibles de dépôt qui ne prennent pas en charge d'autres types, sauf s'il n'y a pas d'alternative textuelle logique. Ajoutez toujours ce type text/plain en dernier, car il est le moins spécifique et ne doit pas être préféré.
Dans getData(), setData() et clearData(), le type Text (insensible à la casse) est traité comme text/plain.
Par défaut, lorsqu'une sélection est glissée, les éléments de données suivants sont créés :
text/plain: contient le texte sélectionné. Firefox et Safari trient cet élément aprèstext/html, bien que la spécification exige qu'il soit en premier.text/html: contient le code HTML complet des éléments sélectionnés (avec tous les styles en ligne).
La spécification exige également un autre élément de type application/microdata+json, contenant les microdonnées extraites des éléments de la sélection glissée. Aucun navigateur n'implémente cet élément.
Lorsqu'un élément est déposé dans un champ de texte éditable, tel qu'un <textarea> ou un <input type="text">, l'élément text/plain est copié dans le champ par défaut (sans aucun traitement d'évènement).
Glisser des liens
Les hyperliens glissés doivent inclure des données de deux types : text/uri-list et text/plain. Les deux types doivent utiliser l'URL du lien pour leurs données. Remarque : le type URL est uri-list avec un I, pas un L.
Comme d'habitude, définissez le type text/plain en dernier, comme solution de repli pour le type text/uri-list. Par exemple :
event.dataTransfer.items.add("https://www.mozilla.org", "text/uri-list");
event.dataTransfer.items.add("https://www.mozilla.org", "text/plain");
Pour faire glisser plusieurs liens, séparez chaque lien dans les données text/uri-list avec un saut de ligne CRLF. Les lignes commençant par un dièse (#) sont des commentaires et ne doivent pas être considérées comme des URL. Vous pouvez utiliser des commentaires pour indiquer l'objectif d'une URL, le titre associé à une URL ou d'autres données.
Attention :
La solution de repli text/plain pour plusieurs liens doit inclure toutes les URL, mais aucun commentaire.
Par exemple, cet exemple de données text/uri-list contient deux liens et un commentaire :
https://www.mozilla.org #Un second lien http://www.exemple.com
Lors de la récupération d'un lien déposé, assurez-vous de gérer le cas où plusieurs liens sont glissés, y compris les commentaires.
Dans getData(), setData() et clearData(), le type URL (insensible à la casse) est traité comme text/uri-list. Pour getData(), le résultat ne contient que la première URL de la liste.
Par défaut, lorsqu'un élément HTML <a> est glissé, les éléments de données suivants sont créés :
text/x-moz-url(seulement Firefox) : contient à la fois l'attributhrefet le texte du lien, séparés par un saut de ligne.text/x-moz-url-data(seulement Firefox) : contient uniquement l'attributhref.text/x-moz-url-desc(seulement Firefox) : contient uniquement le texte du lien.text/uri-list: contient l'attributhref.text/html(seulement Chrome et Firefox) : contient le code HTML complet de l'élément<a>(avec tous les styles en ligne).text/plain: contient également l'attributhref. Chrome trie cet élément avanttext/uri-list.
Glisser des images
Le déplacement direct d'images (c'est-à-dire que les données sont le contenu des pixels) n'est pas courant et peut ne pas être pris en charge sur certaines plateformes. À la place, les images sont généralement glissées uniquement par leurs URL. Pour ce faire, utilisez le type text/uri-list comme pour les autres URL. Les données doivent être l'URL de l'image, ou une data: URL si l'image n'est pas stockée sur un site Web ou un disque.
Comme pour les liens, les données pour le type text/plain doivent également contenir l'URL. Cependant, une URL data: n'est généralement pas utile dans un contexte texte, vous pouvez donc choisir d'exclure les données text/plain dans cette situation.
event.dataTransfer.items.add(imageURL, "text/uri-list");
event.dataTransfer.items.add(imageURL, "text/plain");
Par défaut, lorsqu'un élément HTML <img> est glissé, les éléments de données suivants sont créés :
text/x-moz-url(seulement Firefox) : contient à la fois l'attributsrcet le texte alternatif (ou lesrcà nouveau si le texte alternatif est vide), séparés par un saut de ligne.text/x-moz-url-data(seulement Firefox) : contient uniquement l'attributsrc.text/x-moz-url-desc(seulement Firefox) : contient uniquement le texte alternatif (ou lesrcsi le texte alternatif est vide).text/uri-list: contient l'attributsrc.text/html: contient le code HTML complet de l'élément<img>(avec tous les styles en ligne).text/plain(seulement Firefox) : contient l'attributsrc.
Safari crée également un élément de fichier contenant les données de l'image, avec le type MIME approprié, tel que image/png.
Glisser des éléments
Lorsque l'élément glissé est un élément arbitraire avec draggable="true", les données à définir dépendent de ce que vous souhaitez transférer.
Une façon courante de transférer l'élément est d'utiliser le type text/html contenant le code source HTML sérialisé, que le destinataire peut ensuite analyser et insérer. Par exemple, il serait approprié de définir ses données sur la valeur de la propriété outerHTML d'un élément. text/xml peut également être utilisé, mais assurez-vous que les données sont bien formées en XML.
Vous pouvez également inclure une représentation en texte brut des données HTML ou XML en utilisant le type text/plain. Les données doivent être uniquement le texte sans aucune des balises ou attributs source. Par exemple :
event.dataTransfer.items.add("text/html", element.outerHTML);
event.dataTransfer.items.add("text/plain", element.innerText);
Vous pouvez également utiliser d'autres types que vous inventez à des fins personnalisées. Efforcez-vous d'inclure toujours une alternative text/plain, sauf si l'objet glissé est spécifique à un site ou une application particulière. Dans ce cas, le type personnalisé garantit que les données ne peuvent pas être déposées ailleurs.
Glisser des fichiers depuis un explorateur de fichiers du système d'exploitation
Lorsque l'élément glissé est un fichier, un élément de type file est ajouté aux données de glissement. Le type est défini sur le type MIME du fichier (tel que fourni par le système d'exploitation), ou application/octet-stream si le type est inconnu. Actuellement, les fichiers glissés ne peuvent provenir que de l'extérieur du navigateur, comme depuis un explorateur de fichiers.
Firefox ajoute également un élément de texte non standard de type application/x-moz-file contenant le chemin complet du fichier sur le système de fichiers de l'utilisateur·ice. Sauf dans le code privilégié (comme une extension), sa valeur est une chaîne de caractères vide.
Glisser des fichiers vers un explorateur de fichiers du système d'exploitation
Ce qui peut être transféré hors du navigateur dépend principalement du navigateur et de l'endroit où il est glissé. Glisser des images vers le système de fichiers local est couramment pris en charge et entraîne le téléchargement de l'image.
Chrome prend en charge le type non standard DownloadURL. La charge utile doit être du texte sous la forme <MIME type>:<file name>:<file URL>. Par exemple :
event.dataTransfer.items.add(
"DownloadURL",
"image/png:example.png:data:image/png;base64,iVBORw0K...",
);
Cela permet de télécharger un fichier arbitraire lorsqu'il est glissé vers l'explorateur de fichiers, ou, lorsqu'il est déposé dans une autre fenêtre de navigateur, comme si un fichier était en cours de dépôt (bien que des restrictions CORS puissent s'appliquer). Voir Glisser des fichiers comme Gmail (angl.) pour un cas d'utilisation pratique.