web-dev-qa-db-fra.com

Déterminer ce qui est glissé depuis les événements dragenter et dragover

J'essaie d'utiliser l'API déplaçable HTML5 (même si je m'en rends compte a ses problèmes ). Jusqu'à présent, le seul showstopper que j'ai rencontré est que je ne peux pas trouver de moyen de déterminer ce qui est glissé lorsqu'un événement dragover ou dragenter se déclenche:

el.addEventListener('dragenter', function(e) {
  // what is the draggable element?
});

Je me rends compte que je pourrais supposer que c'est le dernier élément à déclencher un événement dragstart, mais ... multitouch. J'ai également essayé d'utiliser e.dataTransfer.setData à partir de dragstart pour attacher un identifiant unique, mais apparemment, ces données sont inaccessibles à partir de dragover/dragenter:

Ces données ne seront disponibles qu'une fois qu'une baisse se produit pendant l'événement de baisse.

Alors, des idées?

Mise à jour: Au moment de la rédaction de ce document, le glisser-déposer HTML5 ne semble pas être implémenté dans les principaux navigateurs mobiles, ce qui fait le point sur le moot multitouch dans entraine toi. Cependant, j'aimerais une solution qui soit garantie de fonctionner sur n'importe quelle implémentation de la spécification , qui ne semble pas empêcher plusieurs éléments d'être glissés simultanément.

J'ai posté ne solution de travail ci-dessous, mais c'est un hack laid. J'espère toujours une meilleure réponse.

63
Trevor Burnham

La réponse courte à ma question se révèle être la suivante: non. Le spécification WHATWG ne fournit pas de référence à l'élément déplacé (appelé "noeud source" dans la spécification) dans le dragenter, dragover ou dragleave événements.

Pourquoi pas? Deux raisons:

Tout d'abord, comme Jeffery le souligne dans son commentaire , la spécification WHATWG est basée sur l'implémentation par glisser-déposer d'IE5 +, qui était antérieure aux appareils multi-touch. (Au moment de la rédaction de ce document, aucun navigateur multi-touch majeur n'implémente le glisser-déposer HTML.) Dans un contexte "à simple toucher", il est facile de stocker une référence globale à l'élément déplacé actuel sur dragstart.

Deuxièmement, le glisser-déposer HTML vous permet de faire glisser des éléments sur plusieurs documents. C'est génial, mais cela signifie également que fournir une référence à l'élément glissé dans chaque événement dragenter, dragover ou dragleave n'aurait pas de sens; vous ne pouvez pas référencer un élément dans un autre document. C'est une force de l'API que ces événements fonctionnent de la même manière, que le glissement provienne du même document ou d'un autre.

Mais l'impossibilité de fournir des informations sérialisées à tous les événements de glissement, sauf via dataTransfer.types (comme décrit dans ma solution de travail réponse), est une omission flagrante dans l'API. J'ai soumis une proposition de données publiques dans les événements de glisser au WHATWG, et j'espère que vous exprimerez votre soutien.

21
Trevor Burnham

Je voulais ajouter une réponse très claire ici afin qu'elle soit évidente pour tous ceux qui se promènent ici. Cela a été dit plusieurs fois dans d'autres réponses, mais ici, c'est aussi clair que je peux le dire:

dragover N'A PAS LES DROITS pour voir les données dans l'événement glisser.

Ces informations ne sont disponibles que pendant le DRAG_START et le DRAG_END (drop).

Le problème est que ce n'est pas évident du tout et exaspérant jusqu'à ce que vous arriviez à lire suffisamment en profondeur sur les spécifications ou les endroits comme ici.

SOLUTION DE CONTOURNEMENT:

Pour contourner ce problème, j'ai ajouté des clés spéciales à l'objet DataTransfer et les ai testées. Par exemple, pour améliorer l'efficacité, je voulais rechercher des règles de "drop target" lorsque mon glisser-déposer a commencé au lieu de chaque fois qu'un "glisser-déplacer" s'est produit. Pour ce faire, j'ai ajouté des clés identifiant chaque règle sur l'objet dataTransfer et testé celles avec "contient".

ev.originalEvent.dataTransfer.types.includes("allow_drop_in_non_folders")

Et des choses dans le genre. Pour être clair, ce "comprend" n'est pas une solution miracle et peut devenir un problème de performance en soi. Prenez soin de comprendre votre utilisation et vos scénarios.

53
bladnman

Une solution (très inélégante) consiste à stocker un sélecteur en tant que type de données dans l'objet dataTransfer. Voici un exemple: http://jsfiddle.net/TrevorBurnham/eKHap/

Les lignes actives ici sont

e.dataTransfer.setData('text/html', 'foo');
e.dataTransfer.setData('draggable', '');

Puis dans les événements dragover et dragenter, e.dataTransfer.types contient la chaîne 'draggable', qui est l'ID nécessaire pour déterminer quel élément est déplacé. (Notez que les navigateurs nécessitent apparemment que les données soient définies pour un type MIME reconnu comme text/html également pour que cela fonctionne. Testé dans Chrome et Firefox.)

C'est un vilain et moche hack, et si quelqu'un peut me donner une meilleure solution, je lui accorderai volontiers la prime.

Mise à jour: Une mise en garde mérite d'être ajoutée est que, en plus d'être inélégante, la spécification indique que tous les types de données seront convertis en minuscules ASCII. Soyez donc averti que les sélecteurs impliquant des majuscules ou unicode se briseront. solution de Jeffery contourne ce problème.

6
Trevor Burnham

Compte tenu des spécifications actuelles, je ne pense pas qu'il existe de solution qui ne soit pas un "hack". Pétition le WHATWG est un moyen de résoudre ce problème :-)

Extension de la "solution (très inélégante)" ( démo ):

  • Créez un hachage global de tous les éléments en cours de glissement:

    var dragging = {};
    
  • Dans le gestionnaire dragstart, attribuez un ID glisser à l'élément (s'il n'en a pas déjà un), ajoutez l'élément au hachage global, puis ajoutez l'ID glisser comme type de données:

    var dragId = this.dragId;
    
    if (!dragId) {
        dragId = this.dragId = (Math.random() + '').replace(/\D/g, '');
    }
    
    dragging[dragId] = this;
    
    e.dataTransfer.setData('text/html', dragId);
    e.dataTransfer.setData('dnd/' + dragId, dragId);
    
  • Dans le gestionnaire dragenter, recherchez l'ID de glissement parmi les types de données et récupérez l'élément d'origine à partir du hachage global:

    var types = e.dataTransfer.types, l = types.length, i = 0, match, el;
    
    for ( ; i < l; i++) {
        match = /^dnd\/(\w+)$/.exec(types[i].toLowerCase());
    
        if (match) {
            el = dragging[match[1]];
    
            // do something with el
        }
    }
    

Si vous gardez le hachage dragging privé de votre propre code, le code tiers ne pourrait pas trouver l'élément d'origine, même s'il peut accéder à l'ID de glisser.

Cela suppose que chaque élément ne peut être glissé qu'une seule fois; avec le multi-touch, je suppose qu'il serait possible de faire glisser le même élément plusieurs fois en utilisant des doigts différents ...


Mise à jour: Pour permettre plusieurs traînées sur le même élément, nous pouvons inclure un nombre de traînées dans le hachage global: http: // jsfiddle .net/jefferyto/eKHap/2 /

4
Jeffery To

Vous pouvez déterminer ce qui est glissé au début du glissement et l'enregistrer dans une variable à utiliser lorsque les événements de glisser/glisser sont déclenchés:

var draggedElement = null;

function drag(event) {
    draggedElement = event.srcElement || event.target;
};

function dragEnter(event) {
    // use the dragged element here...
};
3
tjscience

Dans l'événement drag, copiez event.x et event.y à un objet et définissez-le comme la valeur d'une propriété expando sur l'élément glissé.

function drag(e) {
    this.draggingAt = { x: e.x, y: e.y };
}

Dans les événements dragenter et dragleave, recherchez l'élément dont la valeur de propriété expando correspond à event.x et event.y de l'événement en cours.

function dragEnter(e) {
    var draggedElement = dragging.filter(function(el) {
        return el.draggingAt.x == e.x && el.draggingAt.y == e.y;
    })[0];
}

Pour réduire le nombre d'éléments que vous devez consulter, vous pouvez effectuer le suivi des éléments en les ajoutant à un tableau ou en affectant une classe dans l'événement dragstart, et en l'annulant dans l'événement dragend .

var dragging = [];
function dragStart(e) {
    e.dataTransfer.setData('text/html', '');
    dragging.Push(this);
}
function dragEnd(e) {
    dragging.splice(dragging.indexOf(this), 1);
}

http://jsfiddle.net/gilly3/4bVhL/

Maintenant, en théorie, cela devrait fonctionner. Cependant, je ne sais pas comment activer le glissement pour un appareil tactile, donc je n'ai pas pu le tester. Ce lien est au format mobile, mais le toucher et la diapositive n'ont pas entraîné le démarrage du glissement sur mon Android. http://fiddle.jshell.net/gilly3/4bVhL/1/show/

Modifier: D'après ce que j'ai lu, il ne semble pas que HTML5 déplaçable soit pris en charge sur tous les appareils tactiles. Pouvez-vous faire fonctionner le draggable sur n'importe quel appareil tactile? Sinon, le multi-touch ne serait pas un problème et vous pouvez simplement stocker l'élément glissé dans une variable.

3
gilly3

Pour vérifier s'il s'agit d'un fichier, utilisez:

e.originalEvent.dataTransfer.items[0].kind

Pour vérifier le type, utilisez:

e.originalEvent.dataTransfer.items[0].type

c'est-à-dire que je veux autoriser un seul fichier jpg, png, gif, bmp

var items = e.originalEvent.dataTransfer.items;
var allowedTypes = ["image/jpg", "image/png", "image/gif", "image/bmp"];
if (items.length > 1 || items["0"].kind != "file" || items["0"].type == "" || allowedTypes.indexOf(items["0"].type) == -1) {
    //Type not allowed
}

Référence: https://developer.mozilla.org/it/docs/Web/API/DataTransferItem

2
user2272143

D'après ce que j'ai lu sur MDN, ce que vous faites est correct.

MDN répertorie certains types de glisser recommandés , tels que text/html, mais si aucun ne convient, stockez simplement l'ID sous forme de texte en utilisant le type 'text/html', ou créez votre propre type, tel que ' application/node-id '.

0
Jules