Spis treści:

Kategoria:HTMLJavaScript


Przeciągnij i upuść w HTML

Mama mówi do synka:
- Jasiu, dlaczego już się nie bawisz z Kaziem?
- A czy ty mamusiu chciałabyś się bawić z kimś, kto kłamie, bije i przeklina?
- Oczywiście, że nie.
- No widzisz. Kaziu też nie chce!

Ukłon w stronę myszki

Komputerowa myszka jest z nami tak długo, że nawet nie pamiętamy od kiedy. Uznaje się, że był to rok 1963. Wtedy do Douglas Engelbart pokazał światu to wspaniałe urządzenie, które zrewolucjonizowało sposób pracy z wieloma urządzeniami. Jakiś czas później sposób działania myszy został wykorzystany w komputerach osobistych i było to dobre. Do tego stopnia dobre, że powszechnym stało się twierdzenie: wymyśl coś lepszego niż myszka, a świat wydepcze ścieżkę do twojego domuW nawiązaniu do słynnej wypowiedzi dotyczącej pułapki na myszy: Build a better mousetrap, and the world will beat a path to your door.. Przyznajmy, pojawiały się jakieś kuleczki, jakieś touchpady - to jednak nie to samo. Nie ma to jak myszka w ręce. Konsekwencją wprowadzenia myszki pod strzechy jest mechanizm Drag&Drop, znany w naszym języku jako operacja przeciągnij i upuść. To właśnie tą operacją i jej obsłudze w HTML zamierzam się dzisiaj zająć. Przejdźmy do przykładu.

Drag&Drop elementów HTML

Jak mawiają niektórzy - dobry przykład nie jest zły. Postanowiłem cały mechanizm pokazać na przykładzie prostej, choć niedokończonej gry edukacyjnej. Zadaniem gracza jest przeciągnięcie flag państw do odpowiednich prostokątów. Popatrzmy na przykład:

EuropaAmeryka

Można się pobawić samemu i sprawdzić swoją wiedzę. Obrazki nie są zbyt wyraźne, ale uznajmy to za utrudnienie. Gdy przerzucanie flag już się znudzi, można przejść do implementacji.

Kod aplikacji i kluczowe zdarzenia

Zanim przejdę do wyjaśnień, popatrzmy na pełny listing. Umieściłem kod całej strony, gotowy do pobrania, zapisania na dysku lokalnym i eksperymentów.

<html>
<head>
<title>Przeciągnij i upuść w HTML</title>
</head>
<body>
<div id="flagsGame">
<table>
<tr><th>Europa</th><th>Ameryka</th></tr>
<tr>
    <td><div class="local-drag-drop-area" id="europe"/></td>
    <td><div class="local-drag-drop-area" id="america"/></td>
</tr>
</table>
    <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABVklEQVR4XqWTPUhCURzFz60wSUUlammwcGqKoKAhWkwIB63hjQ5tLuri0CK4W4ibH4gOQoM0lFBQSF9QjS4uLU45PCGJPrDXq3/3Sfh4+IJe/uDw4w4HDpd7GRFhGBiPicfaszEknhcGYMrrTYmCsIJK5Q56FvZ3oYfz/GBa8Wwmc0OxWIWq1Yauf0PpjgEYLZevEY1uoFi6QsC/hBK3n7tYvEQgsIynrW3NgJFJJz4a91C64LhTqQsShAxls7fk8ewNWA9ZlknpKgtQKNQQDvuQy9UQDK4N+HHBo715hx1vYrt/dicSh+Ry7VA8fkIOR3TAenQ6HXVBPn+KUGgTyeQRIhEf0uljjdsTc9oFNitE9qUuaDabZBTeURe05ldhMVtA3Xf8BWYeR6v7qi6o1+tkFN7pL5DExXWc4V9IjMvOM8NjgzGeeR7Yz2sy9WyMTx5p6O/8DcFzSMIBjfonAAAAAElFTkSuQmCC"/>
    <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABzElEQVR4Xp2TTWtTURCGnzn3nJvmpgYs+EGxuNCiC8FF6dKdC8G9/0EQEe0yf0BXgiv/gGA2WoXWIBWhomK0pmL8IJhAGyIGUmi9xdgkZ/RCPwhUiHngZRYzvPAOM0I/wmAo20gul8Na6/Qv/2vQ6XS6VCqVjzok5Q/v7ts4jqM43qTZWGK1Ms/k1AzjR8coFJvczFdIp4LdbF5AgJ6CGEf19cOs9Rro91qB6tI1jowZVkp1Dp67y4gzjB2wpMOAPQdQQFXBWtZGU8qbYulb6eVtLc2O66fCaX16b1rr9RUdhMWFx3M2dAbVU6x3ptCNEtnj1/9GOMydfIWrN55D2u2t1gOi0APEwsYrbGA8Z6YvsH7iLFtbW6TSERKkuHR+gsn8RZwVdhDd9gCsG2HhSRv7YrlJq9vCq8MQouJR/4PACNnRkD4UdNfAELoAe3lmEaIGSBfEgAxwAerBO4iLsForV3VInhVm5+yVW285NPEL0S4igBhA2RcVPB5VMEFIbfmz2Mbab2K7iahHBvgG3dmDdGj97Bi+lN/XdEgePcjPSxRFJzOZzDHnHIlEBFWVfQOI0Ov1EiWPRLvd/gpA0khkjEnqv5T0++YB/gBobYgmRygj/wAAAABJRU5ErkJggg=="/>
    <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAFiSURBVDhPxVMtU8NAFNxrAggGJK4YWgkCj+E3IDEgKvgH0OkMHsNvqakAg8JSprY1gGCgM6U0NEkvOfZd7zKhAkEFb2az7z527929iTIMLBEVx38OxeN/VJA79iHj3zA3aDaZMsk5JcgyGJ2RNVkDhJnNbF7mSbvtDS5gnl+s0Iq56LlAKpySCY5VtYpxp0MD7jPqnOc/ERm+RjmmI42NrRjBGk8yFBqKLFye00Bt42N8i4rcQ4SsE5NII7tmehUiej9EnHjhAiBglQzXBSljhuDhGPpVQ521sNLfQTJUJWG5EjHQ9rmsgTFSqka4V8dnnbOXLQS7+wjNKucTJ/I8Bx9ibiAf737ff8T6QYr4KMFd9wbxZlSslVmuKwdKg1RCfdBoIB8MgOLFudG9dsE+1zRgi1WthmG3CzWlQXhyahdkkywWuYjKvRe4Vgveej2oiAbSiTIkFucW4eP/f6YlDYBvoZpmO2D8zScAAAAASUVORK5CYII="/>
    <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAD0SURBVDhPxVNLDoJADO0Q11zAHTfTC3AT78B1XHsAV2xcGBKN4Tfje9MZQJCY6MKXPF4p7ZuWgHGA/IAk6NcwrqpeJrDTi7U+tNQVqkGaskOm22johhx1zltZjiswEaHxWPjuZMJ2nRosm1U/kUaJeimYxMGLwjXCQUyPenM4iMM+DiMJSB3YtiBjaiDyZruVqijUQPJc3PmMh40Ii31hI66BNlDezzTJMrkej7pCfOCbWUDWQd80xykxb3gHcdRpYWRdv95zMtb0mB0wDwyw2e2HHaXrx5iF0Tyc6hsDL6eTmDsM/Nc2ITHPzRnx/5/pRwORJ+YshRh+ShS7AAAAAElFTkSuQmCC"/>
    <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAGfSURBVDhPxZO9alVREIXXubkBK7G08PqDomBhikSQBERMNIIXtPQpbFL4Fr6CWAu39AUE0dZHscjZ/+M3+5xLSsEUblhnzZ49a2b2zxmMoUuMxcz/PIb1+thUR6k1aWepVJpSLrKWWcb3lzGcnh7a2dmHPmkkcdRawJaLSpmRs0qfZ2XszeaLlovFtIsfP39NAQT6YkpJKSbFFBXjBUIIiuDN2+ddt1jxuUbVFaIV4hXCm+AWwbdj0B34LoJ7Yex4MAL81+nCx/JdjtqvVfskMIS2ZYSOFgCCyYZHOAXtck5L78AywSToooTYg4AHTxgv5lRvEYzEuK4n8D44mKnqXMmrbwVb8fl5X69ucy5Ws6pvIR/yvW+ynYaTW8gOOpptKzYzdTJxcHM+GBS+eQePJLtBwEFVewyeID6Cj+jqKUmeFbVjrvQE30uSvcL3Gs3DQe0F7+Dr5z07Wb/n4XhDbAO2lngPlDLOpM+dSWjYtODAoc2n7xo2H/fsClo0bEPijPr7s6u0F7BZS/gr627v8mwafuH7je///0yXTCD9AdsChScBJJF+AAAAAElFTkSuQmCC"/>
    <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABgElEQVR4XqXTP2sUQRjH8e/c7K45Tzd4cjZXphEiEa6K9trHJoWvIWVK0Xdg70uwiJVvIKVwQkRFCzHm0IgQvds9N/PnZkY52NshGOHIB4aneOD3MDwzIoTARQggA67M63IMMBVAb2vv8Q/O2Nt6Quzbg23O6r94fiMBOi44bvfW+J/s1joAQkAIoA/eAHQSQFrnKE3FT1VyHvvliJrsXiNYCyATAG0NE/2b4u+JOWNwakqWd/FFQU1IyUxrABIApdV8emEq6q2UVvHy6Q6jj694uPsMP56wEMAo1QScKs1nvlPoitqHt68x0xNY8bx7v8/a4SG1Vp6jZCsOqGill/DBUzupjhh1byLbOa7XJ/imF2YWZXx0BaVJOyk+elR3B/dZbV/HecFgY5Nj/4gFO0Nb2wQYpUjCZQSNvN3lzuAeC6LphhBQRkcBxrBCIJMJ5xFpymJC8OgowHjnGX86JjYcDomNxr/4ByOAVaAPXGU5JfBVABLI5nU5DjAX/s5/AEU/skY7HQsIAAAAAElFTkSuQmCC"/>
</div>
<script>
function validDropArea(e){
    return (' ' + e.target.className + ' ').indexOf(' local-drag-drop-area ') > -1;
}
function attachDropEvents(div){
    div.ondragover = function(e){
        if (validDropArea(e))
            e.preventDefault();
    };
    div.ondrop = function(e){
        var id = e.dataTransfer.getData("Text");
        e.target.appendChild(document.getElementById(id));
        e.target.ondragleave(e);
    };
    div.ondragenter = function(e){
        if (validDropArea(e))
            e.target.style.border = "1px solid Red";
    };
    div.ondragleave = function(e){
        if (validDropArea(e))
            e.target.style.border = "1px solid #888";
    };
}
window.addEventListener("load", function () {
    for (var i=0, img, images = document.getElementsByTagName("img"); img = images[i]; i++){
        img.draggable = true;
        img.id = "flag"+i;
        img.ondragstart = function(e){
            e.dataTransfer.setData("Text",e.target.id);
        };
    }
    for (var i=0, d, dl = document.getElementsByClassName("local-drag-drop-area"); d = dl[i]; i++){
        attachDropEvents(d);
    }
}, false);
</script>
<style type="text/css">
#flagsGame .local-drag-drop-area {
  width:100px;
  height:50px;
  border:1px solid #888;
}
#flagsGame img {
  margin:4px;
}
</style>
</body>
</html>

Struktura strony jest prosta. W górnej części znajduje się tabelka z dwoma komórkami. Określają one geograficzny obszar Europy lub Ameryki. To tam można przenosić flagi. Same flagi znajdują się poniżej.

Obrazki w postaci Base64

Niektórzy dostrzegli pewnie dziwne krzaki w kodzie obrazków. Elementy reprezentujące flagi nie są reprezentowane przez tradycyjne znaczniki img z adresem pliku leżącym na serwerze. Uznałem, że w ten sposób łatwiej będzie pobrać cały kod - wszystko jest bowiem w jednym pliku. To dość powszechna technika, bo pozwala ograniczyć liczbę żądań. W tradycyjnym rozwiązaniu mielibyśmy tych żądań więcej: żądanie strony HTML, żądanie skryptu, żądanie pliku CSS i 6 żądań obrazków. Nic nie stoi na przeszkodzie, aby taki plik HTML z wszystkim w środku rozdzielić. Nie ma to wpływu na mechanizm Drag&Drop.

Definiowanie obiektów, które można przenosić

Domyślnie, ze względów historycznych i zdroworozsądkowych, nie można przenosić żadnych elementów HTML na stronie. Wymaga to dwóch prostych czynności:

  • ustawienie atrybutu draggable,
  • obsługa zdarzenia ondragstart.

Pierwszy etap jest prosty. Drugi wymaga dalszych wyjaśnień. Gdy przeglądarka uzna, że element jest przenoszony, wywoła wspomniane zdarzenie. To tutaj należy wskazać, co tak naprawdę zamierzamy przenosić. Do kontenera Drag&Drop, schowka, przekazujemy identyfikator elementu w postaci tekstowej. Służy do tego funkcja setData. Pierwszy argument określa typ danych, drugi - wartość. Ten kontener może być potem sprawdzany przez inne elementy pod kątem obecności obsługiwanego przez nie typu. Jeżeli kontrolka przyjmująca obiekty Drag&Drop uzna, że potrafi rozpoznać typ tekstowy i odpowiednio go obsłużyć, zgłasza przeglądarce gotowość. Przeglądarka informuje o tym użytkownika zmieniając kształt kursora myszy. W pokazanym przypadku wystarczającą informacją jest identyfikator przenoszonego obiektu. To wystarczy, aby przestawić element DOM na stronie. Dlatego też podczas wczytywania strony każdy z obrazków dostaje swój unikatowy kod.

Definiowanie elementów, które mogą przyjąć inne obiekty

Podobną, choć nieco inną pracę należy wykonać po stronie obiektów, na które coś upuszczamy. W pokazanym przykładzie obiekty takie oznaczone są odpowiednią klasą - local-drag-drop-area. Nazwa nie ma znaczenia i służy tylko identyfikacji obiektów. Po wczytaniu się strony skrypt odnajduje wszystkie takie elementy i ustawia w nich odpowiednie funkcje obsługi zdarzeń. Operacja upuszczania wymaga obsługi przynajmniej dwóch zdarzeń:

  • ondragover - tutaj wskazujemy, czy obiekt może przyjąć inne obiekty. Domyślnie obiekty nie mogą tego zrobić. Jeżeli funkcja sprawdzająca odnajdzie na liście klas wartość local-drag-drop-area, domyślna faunkcja obsługi zdarzeń przeglądarki zostanie pominięta. Służy do tego metoda preventDefault obiektu zdarzenia.
  • ondrop - tutaj podejmujemy obsługę operacji upuszczania. Tak jak wcześniej ustawialiśmy wartość kontenera funkcją setData, tak teraz ją pobieramy funkcją bliźniaczą - getData. Oczekujemy wartości tekstowej, i takiej też żądamy. Te nasze magiczne operacje to przeniesienie elementu DOM z jego dotychczasowej lokalizacji do nowego elementu.

Te dwie operacje wystarczają do zaimplementowania całego mechanizmu. Uznałem jednak, że warte pokazania są jeszcze dwa zdarzenia:

  • ondragenter - zdarzenie wywoływane w momencie przesunięcia obiektu przenoszonego nad obiekt dopuszczający operację upuszczania. To dobre miejsce, aby w jakiś sposób wskazać użytkownikowi obszar upuszczania, ewentualnie efekt upuszczenia obiektu.
  • ondragleave - zdarzenie wywoływane w momencie wyjechania myszką poza obszar dopuszczający operację Drop. To znakomite miejsce na anulowanie operacji wykonywanej w zdarzeniu ondragenter.

W pokazanym przykładzie pierwsza operacja służy do podświetlenia ramki, druga do przywrócenia ramki do stanu pierwotnego.

Dalsze kroki w Drag&Drop

Operacja Drag&Drop w najprostszym wydaniu jest bardzo prosta. W bardziej skomplikowanych przypadkach pomiędzy elementami HTML przenoszone są bardziej skomplikowane dane, w innych, być może prywatnych formatach. Obiekty przyjmujące przenoszony obiekt mogą akceptować jedne formaty i odrzucać inne. Możliwości są naprawdę duże. Idąc dalej, istnieje również możliwość przerzucenia pliku spoza przeglądarki i upuszczenia go na dowolnym elemencie obsługującym taką operację. To alternatywny sposób wczytywania załączników do dokumentów i obrazków do wpisów. W wielu sytuacjach znacznie wygodniejszy niż ręczne wskazywanie kilku plików z tego samego katalogu. Sposób obsługi Drag&Drop dla plików z pulpitu został pokazany w artykule Załączniki plikowe - Drag, Drop, HTML.

Kategoria:HTMLJavaScript

, 2014-02-08

Brak komentarzy - bądź pierwszy

Dodaj komentarz
Wyślij
Ostatnie komentarze
bardo ciekawe , można dzięki takim wpisom dostrzec wędkę..
Bardzo dziękuję za jasne tłumaczenie z dobrze dobranym przykładem!
Dzieki za te informacje. były kluczowe
Dobrze wyjaśnione, dzięki !