HTML5 Drag & Drop, Teil III: der indirekte Drop-Nachweis

n

nDieser Artikel ist Teil einer Serie:n

    n

  1. Teil 1: Dateienn
  2. Teil 2: Elementen
  3. Teil 3: Drop Effectn

n

nn

nDer Begriff „Planet“ geht auf das griechische Wort πλανήτης zurück, das ganz grob übersetzt „Umherschweifender“ bedeutet. Das Wort hat sich durchgesetzt, weil schon die alten Griechen die Planeten beobachten konnten und erkannten, dass diese sich am Himmel bewegen, im Gegensatz zu ganz normalen Sternen. Nur der Planet Neptun ist in der Antike nie entdeckt worden, weil er einfach viel zu weit entfernt ist, um ohne weiteres als bewegtes Objekt erkannt zu werden. Einem findigen Mathematiker namen Urbain Le Verrier fiel allerdings 1846 auf, dass die Umlaufbahn des bis dahin als äußerstem Planeten bekannten Uranus ein wenig seltsam war. Uranus verhielt sich so, als gäbe es jenseits seiner Bahn noch eine weitere beträchtliche Gravitationsquelle – sowas wie einen großen Planeten. Le Verrier rechnete aus, wo dieser mysteriöse Extra-Planet zu einer bestimmten Uhrzeit zu sehen sein müsste, setzte jemanden vor ein auf die fragliche Stelle gerichtetes Teleskop und siehe da– der Planet Neptun ward entdeckt. Und wenn wir die Drag & Drop-API von HTML5 komplett bändigen wollen, müssen wir genau wie Monsieur Le Verrier einen indirekten Nachweis führen, nur eben über Geschehnisse im Browser und nicht über astronomische Objekte.nnnn

Sinn und Zweck des Drop Effect

nn

nEine Drag & Drop-Operation kann vieles bedeuten. Wenn Element A auf Element B gezogen wird, könnte das eine Kopier- oder auch eine Verschiebe-Operation sein. Man kennt das vom Datei-Manager seines Betriebssystems – je nachdem welche Taste man gedrückt hält, wird eine Datei verschoben oder kopiert oder macht ganz etwas anderes. Die HTML5-API erlaubt es, die mit einer Operation verbundenen Intention (kopieren, verschieben etc.) durch verschiedene Event-Stufen zu transportieren, ähnlich wie Daten transportiert werden. Und ähnlich wie die Daten lässt sich diese Intention bei einigen der Events im Rahmen einer Drag-Operation auslesen – natürlich nicht bei allen Events und auch nicht bei den gleichen Events wie die Daten, aber es geht. Gemäß der Spezifikationen lässt sich die Intention bei den Events dragenter und dragover sowie ggf. bei drop und dragend auslesen, wobei sie jeweils im Event-Objekt in der Eigenschaft dropEffect zu finden ist.nn

nDer dropEffect kann die Werte none, copy, move und link haben, die für jeweils eine Intention stehen. Wie das funktioniert kann man am besten einer einer simplen Demo mit gezogenem Text sehen. Nicht vergessen: die Drag & Drop-API von HTML5 ist die gleiche Funktionalität, die im Browser auch die ganz normalen Drag-Operationen abwickelt. Demnach müsste man in dieser Fiddle unterschiedliche Werte gemeldet bekommen, je nachdem wie man Text auf das Ziel-Div zieht. Drückt man während der Operation die jeweilige Modifikator-Taste (variiert je nach OS; meist ist es ctrl oder alt), so erhält man copy, andernfalls move – immer vorausgesetzt, der Browser verhält sich diebezüglich standardkonform, was Mitte 2014 einzig beim Firefox der Fall ist.nn

nDiese kleine Demo ist kein sinnvolles Beispiel für die Nutzung des Drop Effect, zeigt aber den Gedanken hinter dieser Eigenschaft. Wenn wir den erst mal verstanden haben, können wir den Drop Effect für unsere eigenen Zwecke in unseren eigenen Drag-Operationen nutzen, denn wir können ihn auch selbst setzen … oder besser gesagt beeinflussen.nnnn

Den Drop Effect beeinflussen

nn

nBei einer selbstgebauten Drag & Drop-Operation wie unserem Endprodukt aus Teil 2 der Artikelserie können wir im dragstart-Event uns selbst den Drop Effect für unsere Operation aussuchen. Das Problem ist, dass das nicht ganz so einfach ist, wie man vielleicht spontan hoffen mag. Das, was später der Drop Effect wird, wird über eine Eigenschaft namens effectAllowed festgelegt, die nicht die gleichen Werte wie der spätere dropEffect hat. Stattdessen akzeptiert er die Werte none, copy, copyLink, copyMove, link, linkMove, move, all und uninitialized. Jeder dieser Werte resultiert in einem oder mehreren bestimmten Drop Effects, die dieser Tabelle zu entnehmen sind:nn

n

n

n

n

n

n

n

n

n

n

n

effectAllowed mögliche dropEffects
copyn

copyn

moven

moven

linkn

linkn

copyLinkn

copy, linkn

copyMoven

copy, moven

linkMoven

link, moven

alln

copy, link, moven

nn

nEs gibt noch ein paar weitere effectAllowed-nach-dropEffect-Mutier-Pfade, die jedoch für unseren Use Case von gezogenen Elementen erst mal nicht relevant sind. Man erkennt aber an der Tabelle ein System. Jene effectAllowed-Werte, die auch ein gültiger dropEffect-wären, resultieren in eben genau diesem Wert als dropEffect. Die Kombinationswerte wie copyMove lassen als dropEffect sowohl copy als auch move zu – es kommt dann darauf an, wie genau der Nutzer das Element gezogen hat (d.h. welche Tasten während der Operation gedrückt wurden).nn

nAn dieser Stelle können wir an die (z.Z. nur im Firefox funktionierende) Text-Drag-Demo von vorhin andocken, dann das Ganze funktioniert genau so mit Elementen! Man setze beim Start der Drag-Operation einen effectAllowed, der zwei verschiedene dropEffect-Werte zulässt …nn

$('li').on('dragstart', function(evt){n  evt.originalEvent.dataTransfer.setData('text', '');n  evt.originalEvent.dataTransfer.effectAllowed = 'copyMove';n});

nn

n… und schon wird uns je nachdem wie wir das Element ziehen (normal oder mit gedrückter ctrl/alt-Taste) im drop-Event mal der eine, mal der andere dropEffect gemeldet:nn

$('.dropzone').on('drop', function(evt){n  evt.preventDefault();n  $(this).removeClass('valid');n  window.alert(evt.originalEvent.dataTransfer.dropEffect);n});

nn

nIm Firefox ausprobieren! Blöderweise machen es die anderen Browser weniger gut; Chrome und Safari liefern beide beim drop-Event immer none, ganz egal wie der Nutzer das Element durch die Gegend zieht. IE macht es zumindest mit gezogenen Textauswahlen richtig. Je nachdem was wir nun mit dem dropEffect anzustellen gedenken, kann uns das stören oder auch nicht.nnnn

Den Drop Effect verwenden

nn

nDer dropEffect ist selbst in den Browsern, die ihn nur mittelprächtig implementieren nützlich, doch fokussieren wir uns zunächst auf einen dem Standard entsprechenden Use Case. Dieser wird dann in Firefox, Chrome und Safari funktionieren. Man kann mit kleinen Einschränkungen auch andere Browser unterstützen, aber wir fangen erst mal mit dem vollen Programm an.nn

nWir können in das Endprodukt aus Teil 2 der Artikelserie recht einfach einen dropEffect einbauen, indem wir bei dragstart den effectAllowed festlegen und und beim drop den dropEffect auslesen. Das wäre für sich genommen erst mal ziemlich nutzlos. Allerdings könnten wir ja tatsächlich die beiden denkbaren effectAllowed-Werte das machen lassen, was sie versprechen – nämlich die gezogenen Elemente kopieren oder verschieben! Der erste Schritt ist das schon erwähnte Einbauen des effectAllowed in dragstart:nn

$('li').on('dragstart', function(evt){n  var type = $(this).attr('data-type');n  var data = $(this).text();n  evt.originalEvent.dataTransfer.setData(type, data);n  // Kopieren und verschieben zulassenn  evt.originalEvent.dataTransfer.effectAllowed = 'copyMove';n});

nn

nBeim drop-Event könnte man nun den dropEffect auslesen und das gezogene Element entweder klonen und in das Ziel-Element einhängen (dropEffect ist copy) oder einfach das gezogene Element direkt adoptieren (dropEffect ist copy). Das ist allerdings aus einer Reihe von Gründen nicht empfehlenswert – nicht zuletzt, weil wir in der Demo <li>-Elemente ziehen und diese keine direkten Kinder von <div>-Elementen sein dürfen. Besser ist es, das gezogene Element anhand der im dataTransfer-Objekt übertragenen Daten nachzubauen:nn

$('.dropzone').on('drop', function(evt){n  evt.preventDefault();n  $(this).removeClass('valid');n  var key = $(evt.target).attr('data-accept');n  var val = evt.originalEvent.dataTransfer.getData(key);n  // Das gezogene Element als Div "nachbauen"n  $('<div>').text(val).appendTo(evt.target);n});

nn

nDer Code deckt nun den copy-Fall ab, jedes gezogene Element wird geklont. Für den move-Fall können wir das dragend-Event nutzen, das auf dem gezogenen Element feuert. Hier fragen wir ab, ob der dropEffect den Wert move hat und entfernen in diesem Fall einfach das gezogene Element:nn

$('li').on('dragend', function(evt){n  if(evt.originalEvent.dataTransfer.dropEffect === 'move'){n    // Im Fall von Verschiebe-Operation das gezogene Element löschenn    $(evt.target).remove();n  }n});

nn

nFertig! Je nach gedrückter Modifikator-Taste werden die ziehbaren Elemente nun in ihr Ziel-Element kopiert oder verschoben – und das immerhin in Chrome, Firefox und Safari. Und es kommt noch besser: das Script funktioniert jetzt über Browserfenster-Grenzen hinweg! Man kann zwei Firefox-Fenster mit der Fiddle geöffnet haben und von einem Fenster ins andere Elemente ziehen um sie zu kopieren und zu verschieben.nn

nDas Ganze funktioniert aus drei Gründen: zum einen ist die API einfach so gebaut, dass sie ganz allgemei Drag & Drop-Operationen verarbeitet. Wo diese Operationen starten, ob im gleichen Browserfenster, in einem anderen Fenster oder ob es sich um eine vom Desktop gezogene Datei handelt, ist der API egal. Zweitens stören uns die Bugs in Chrome und Safari, die im drop-Event den falschen dropEffect melden nicht, da wir ihn nur im dragend-Event abfragen, wo er korrekt ausgegeben wird. Und drittens anderen ist der Code so geschrieben, dass jedes Event nur mit den Daten arbeitet, die die API ihm bereitstellt. Würden wir z.B. das gezogene Element im drop-Event nicht nachbauen, sondern das wirklich gezogene Element adopieren, so könnte das nicht funktionieren – wir bekommen schließlich in Fenster A keinen Zugriff auf Elemente in Fenster B. Nur die Daten der Drag-Operation werden übermittelt, wobei diese dann auch reichen, um einigermaßen überzeugend ein Verschieben über Fenstergrenzen hinweg zu simulieren.nn

nDer große Haken an unserem bisherigen Werk ist, dass es aktuell nur in den modernsten Varianten von Firefox, Chrome und Safari funktioniert. Das lässt sich auch nicht ohne weiteres ändern, allerdings können wir mit einer kleinen Einschränkung des Funktionsumfangs weitere Browser bedienen.nnnn

Der eingeschränkte Drop Effect als indirekter Drop-Nachweis

nn

nBrowser wie der Internet Explorer liefern, egal welche Tasten man drückt, den falschen dropEffect. Aber immerhin: sie liefern einen, vorausgesetzt es hat ein drop-Event stattgefunden. Damit kann man zwar nicht mehr zwischen Kopier- und Verschiebe-Operationen unterscheiden, aber immer noch Elemente von Browserfenster A nach Fenster B ziehen. So wird der dropEffect zu einem einfachen Signal degradiert, mit dem wir in einem Fenster festellen können, was im anderen passiert ist, so wie Urbain Le Verrier die abweichende Bahn von Uranus das Signal für die Existenz von Neptun war.nn

nDer dropEffect ist laut Standard none wenn das drop-Event nicht stattgefunden hat (weil z.B. die dragover-Handbremse nicht gelöst wurde). Wenn das drop-Event stattgefunden hat, ist der dropEffect einer der Werte aus dem effectAllowed-Setting – im Ideallfall der Wert, der der durch Tastendruck untermauerten Intention entspricht. Der IE macht nur den letzten Teil falsch, d.h. browserfensterübergreifende Operationen klappen noch immer! Es gibt nur keine Unterscheidung zwischen verschiedenen Daten (da ja der IE als Daten-Key ausschließlich text zulässt) und Intentionen. Ein entsprechend vereinfachter Fall funktioniert also auch im Internet Explorer!nnnn

Fazit

nn

nDrag & Drop ist sowohl in nativen Programmen als auch in Webapps ein alter Hut. Die Möglichkeiten im Browser waren aber stets beschränkt. Datenaustausch zwischen Browserfenstern oder einfach nur mehreren Teilen einer App (z.B. iFrames) war kaum möglich. Auch das Ausdrücken von Intention per Tastendruck war bestenfalls knifflig, da ja jedes Betriebssystem-Interface hier seine eigenen Regeln aufstellt. Um hier auf das native Level zu kommen brauchte es zwingend eine Browser-API.nn

nMit HTML5 wird das Web in Sachen Drag & Drop-Fähigkeiten zumindest in die richtige Richtung geschoben. Theoretisch, d.h. auf dem Spezifikations-Papier, ist alles da, was man braucht und die mißratene API ließe sich mit einer kleinen Abstraktionsschicht in den Griff kriegen. Mir ist noch keine entsprechende Library bekannt, allerdings würde der flächendeckende Einsatz auch mit Astraktionsschicht am Intenet Explorer scheitern, der selbst in der neuesten Version gleich mehrere Features nicht unterstützt. Sollte sich das demnächst ändern, sind auch im IE mit Webapps komplizierte Dateisortier-Interfaces oder Multi-Fenster-Apps möglich … vorausgesetzt ein findiger JavaScript-Nerd bringt die API mal in benuztzbare Form.


Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert