Back to site
Since 2004, our University project has become the Internet's most widespread web hosting directory. Here we like to talk a lot about web servers, web development, networking and security services. It is, after all, our expertise. To make things better we've launched this science section with the free access to educational resources and important scientific material translated to different languages.

HTML5 Drag and Drop

HTML5 Drag and Drop kroz razne pretraživače

Remixed version of image by Svartling.

Image Credit: Flickr user svartling

O HTML5 Drag and Drop mnogo se govorilo u poslednje vreme, ali teško je naći zaista korisne informacije o njegovom implementiranju u više različitih pretraživača. I Mozilla, Apple i Microsoft imaju stranice koje opisuju kako da se on koristi, ali njihovi primeri izgleda da funkcionišu samo u njihovim zasebnim pretraživačima (Apple-ov primer čak ne radi ni u sopstvenom! Ažurirano, 11. jan. 2009: Iako nisam bio u stanju da pokrenem ovaj primer u Safari-ju 2.0.4 i 3.1.2 za OS X i 4.0.4 za Windows, dobio sam vest da radi u Safari-ju 4.0.4 na OS X). Remy Sharp-ov odlični članak Native Drag and Drop bio je dobro mesto da od njega počnem - međutim, primeri nisu radili u Internet Exploreru. Takođe sam veoma uživao u Peter-Paul Koch-a duhovitom i podužem spisu Java Script gurua Peter-Paul Koch-a o mukama koje zadaje unakrsni drag and drop gde se on služi kreativnim i živopisnim jezikom da opiše šta misli o standardu, proizvođačima pretraživača, i o WHAT-WG.

Kada normalni ljudi vide da autor Kompatibilnost master tabela negativno reaguje na neku web tehnologiju, verovatno bi pretpostavili da je to dobar znak da treba da je se klone.

Međutim, ja nisam normalan. S imenom kao što je Zoltan, kako bih mogao biti normalan? (i da, to mi je zaista pravo ime).

Dakle, odlučio sam da lično saznam koliko je HTML5 Drag and Drop zaista loš. Istog momenta sam shvatio Koch-ovu reakciju - prodavci pretraživača nisu implementirali sve iste funkcije, a postoji čak i nekoliko začkoljica u tome kako one funkcije koje jesu implementirane rade. Međutim, posle dosta istraživanja, pronašao sam zajednički imenitelj koji dobro deluje, uz pomoć malog JavaScript-a koji poravnava ivice. Uprkos manama implementacije, atestirani HTML5 Drag and Drop nije pretežak za programere da ga koriste u svojim aplikacijama. Ovaj članak objasniće vam kako da to radite, korak po korak, uz mnoge primere. Kad završite s njim, moći ćete da pišete sopstvene korisne drag and drop skripte kao u ovom primeru:

Videti primer HTML5 Drag and Drop u akciji.

Prednosti nad postojećim Drag and drop implementacijama

Koch je u svom blogu pomenuo da "web programeri NE SMEJU (u smislu RFC 2119) da koriste HTML 5 drag and drop. Umesto njega, trebalo bi da koriste old-school skripte . Ja bih rekao da programeri treba da koriste HTML5 drag and drop iz sledećih razloga:

  • Nezavistan od JavaScript okvira: Većina drugih (ali ne sve) drag an drop implementacije vezane su za okvire treće strane, kao Dojo, Prototype ili jQuery.
  • Ugrađena podrška pretraživača: HTML5 Drag and Drop podržan je u Firefox 3.5+, Chrome 3.0+, Safari 3.0+ i Internet Explorer 5.0 (Napomena: nije štamparska greška - HTML5 Drag and Drop zasniva se na radu koji je obavio Microsoft 1999 godina). Pošto je deo HTML5, pretpostavljam da bi i porška Opere trebalo da bude neizbežna
  • Integracija s drugim web aplikacijama: HTML5 specifikacija omogućiće programerima da proizvedu drag and drop skripte koje rade širom ramova, i unakrsno kroz prozore pretraživača
  • Integracija s ne-web aplikacijama: HTML5 specifikacija takođe omogućava korisnicima da prevlače i spuštaju podatke sa i na ne-web aplikacije

Osnove drag and drop-a, korak po korak

S ciljem da poštedim druge programere glavobolja koje sam ja imao tokom dešifrovanja drag and drop-a kroz razne pretraživače, predstavljam sledeći vodič koji pokazuje kako da to uradite u pet jednostavnih koraka. Svaki korak opisaće skup srodnih koncepata, i pokazaće primere, od kojih je većina ugrađena iz prethodnih koraka. Na kraju svakog koraka, obrazložiću bilo kakve probleme i zanimljivosti na koje budem naišao.

Korak 1: Definisanje objekta koji se može prevlačiti

Najpre treba da definišete HTML čvorove koje želite da prevučete. Firefox traži da ovi čvorovi imaju svoj draggable atribut podešen na "true", Internet Explorer traži da čvor bude <a> tag sa (podešenim atributom href ili sa tagom <img>.

<a href="#" id="toDrag" draggable="true">This is a draggable item</a>

Da bih pokazao kako ovo radi, napisao sam vrlo jednostavnu stranicu sa oznakom u <body>:

<!DOCTYPE html>

<html lang="en">
    <head>
        <meta name="generator" content="HTML Tidy, see www.w3.org">

        <title>Test #1: A Simple Draggable Object</title>

      <script type="text/javascript" src="../../shared/js/EventHelpers.js">
      </script>
      <script type="text/javascript" src="../../shared/js/DragDropHelpers.js">
      </script>

        <link rel="stylesheet" type="text/css" media="screen" href=
        "css/test1.css">
    </head>

    <body>
        <h1>Test #1: A Simple Draggable Object</h1>

        <a href="#" id="toDrag" draggable="true">This is a
        draggable item</a>

        <p>Try to drag the red box around. You will see the
        draggable object cloned in every browser except Explorer
        and Chrome.</p>

        <a href=
        "http://www.useragentman.com/blog/2010/01/10/cross-browser-html5-drag-and-drop/">
        Go back to the User Agent Man HTML5 Drag and Drop
        article</a>
    </body>
</html>

Imajte na umu da ova HTML stranica ima dve skripte u <head>:

  • EventHelpers.js: ova skripta implementira cross-pretraživački događaj rukujući rutinama, bez potrebe za JavaScript okvirom. Svi primeri u ovom članku je koriste, ali slobodno refaktorišite sve kodove u ovom članku da biste upotrebili neki okvir treće strane kao što je Dojo, jQuery ili koji god okvir želite.
  • DragDropHelpers.js: između ostalog što ću kasnije obraditi, aktiviraće prevlačenje draggable="true" objekata u Safari-ju i Chrome-u pomoću ugrađene kopije cssQuery Dean-a Edwards-a.To postiže ubacivanjem sledećeg CSS koda u HTML dokument:
    [draggable=true] {
      -khtml-user-drag: element;
      -webkit-user-drag: element;
      -khtml-user-select: none;
      -webkit-user-select: none;
    }

    Prva dva pravila omogućavaju prevlačenje draggable="true" čvorova, dok poslednja dva sprečavaju korisnika da selektuje tekst unutar čvora (što može dovesti do nekih neočekivanih ponašanja u Webkit pretraživačima). -khtml svojstva rade u starijim verzijama Safarija, dok -webkit svojstva rade u novijim verzijama. Bez ovih CSS svojstava, ovi čvorovi se neće moći prevlačiti pri korišćenju Safari web pretraživača

See Example #1: a draggable object

Par napomena:

  1. Primetićete - kada zadržite strelicu miša iznad neke stavke koja se može prevlačiti, da se ona menja u The  "Move" cursor (tj. u kursor "move"). Ovo nije podrazumevano ponašanje web pretraživača - to je nešto što sam ja dodao u DragDropHelpers, jer smatram da je to dobar vizuelni signal da pokaže korisnicima da se objekat može prevući (ako ne želite ovo ponašanje, možete da ga isključite u svojim skriptama ako podesite DragDropHelpers.showMouseoverCue na false
  2. Kako objekti koji se mogu prevlačiti izgledaju u različitim pretraživačima varira. Evo snimaka ekrana kako to izgleda u Windows web pretraživačima (Napomena: iako su svi snimci ekrana napravljeni sa Windows XP, slični rezultati javljaju se kada gledate i na Windows Vista ili Windows 7):
    Firefox (Windows XP) Safari (Windows XP) Explorer & Chrome (Windows XP)
    [Dragging of a link element, Firefox Windows] [Dragging of a link element, Safari Windows] [Dragging of a link element, Explorer Windows]

    Primetite razlike između ova tri pretraživača:

    • Firefox za Windows pokazuje kloniranu verziju objekta koji se može prevući ispod strelice miša kada se objekat vuče, dok Safari prikazuje sivo polje s tekstom: "This is a draggable item". Explorer i Chrome ne pokazuju nikakav efekat. (Kasnije ćemo obraditi ovo pitanje).
    • Pošto nismo ustanovili gde ćemo da prevučemo naš objekat, svi Windows pretraživači promeniće strelicu miša "Not-Allowed" cursor. (tzv. "not-allowed" kursor miša). Ovo je očekivano ponašanje i kasnije ćete videti da to pomaže korisniku da zna gde da spusti objekat.

    Na OS X, Firefox i Safari liče na Firefox za Windows, osim što se kursor "not-allowed" ne pojavljuje:

    Firefox (OS X) Safari (OS X) Chrome (OS X)
    [Dragging of a link element, Firefox Windows] [Dragging of a link element, Safari OS X] Chrome for Mac

    Linux pretraživači isto nemaju kursor miša "not-allowed". Takođe, klonirana verzija objekta koji se može prevlačiti nije transparentna kada se koristi Linux verzija Firefox-a, a Chrome se na Linuxu ponaša skoro isto kao i na drugim platformama (Napomena: sve testiranje na Linuxu obavljeno je koristeći Ubuntu 9.10 sa standardnim Gnome window menadžerom).

    Firefox (Ubuntu) Chrome (Ubuntu)
    Firefox Linux Chrome Linux

    Smetala mi je činjenica da Explorer i Chrome nemaju nikakve vizuelne signale o prevlačenju, pa sam stavio "ispravku" u DragDropHelpers.js koja se može uključiti podešavanjem DragDropHelpers.fixVisualCues na true. Pogledajmo malo refaktorisanu verziju primera #1, gde ubacujemo sledeći kod u <head>:

    <!DOCTYPE html>
    <html lang="en">
       <head>
          <title>Test #1: A Simple Draggable Object (with visual cues added to Explorer and Chrome)</title>
          <script type="text/javascript" src="../../shared/js/EventHelpers.js"></script>
          <script type="text/javascript" src="../../shared/js/DragDropHelpers.js"></script>
          <script type="text/javascript">
          <!--
          DragDropHelpers.fixVisualCues=true;
          -->
          </script>
    
          <link rel="stylesheet" type="text/css" media="screen" href="css/test1.css" />
    
       </head>
       <body>
    
          <h1>Test #1: A Simple Draggable Object (with visual cues added to Explorer and Chrome)</h1>
    
          <a href="#" id="toDrag" draggable="true">This is a draggable item</a>
    
          <p>Try to drag the red box around.  You will see the draggable object cloned in all browsers.</p>
    
          <a href="http://www.useragentman.com/blog/2010/01/10/cross-browser-html5-drag-and-drop/">Go back to the User Agent Man HTML5 Drag and Drop article</a>
    
       </body>
    </html>
    

See Example #1a, a draggable object with visual cue fix for Explorer and Chrome

Voila! Vizuelni signal sada se pokazuje. Objasniću kako ovo funkcioniše na kraju članka.

Korak 2: Podešavanje događaja na objektu koji se može prevlačiti

Sada kada ste definisali objekat kao onaj koji se može prevlačiti, treba da mu pripojite neke događaje tako da pretraživač zna šta treba da radi dok se on prevlači. Postoje tri događaja koja objekat koji se može prevlačiti može da pokrene:

Naziv događaja Opis
dragstart Pokreće se kad korisnik počne da prevlači objekat.
drag Pokreće se svaki put kad se pomeri miš dok se objekat prevlači.
dragend Pokreće se kada korisnik pusti dugme miša dok prevlači objekat.

Programer će pripojiti ove događaje onako kako bi pripojio i bilo koji drugi JavaScript događaj - ja koristim EventHelpers.addEvent, ali vi možete da koristite jQuery-jev bind() metod, prototipov Event.observe() ili šta god da vaš omiljeni okvir nudi za rukovanje dogadjajem. Programeri takođe mogu da koriste old school paralelne događaje (tj. <a href="#" id="toDrag" draggable="true" ondragstart="somefunction();">This is a draggable item</a>), ali ja se trudim da se klonim njih da bih odvojio strukturu dokumenta od ponašanja.

Uzmimo primer u prvom koraku i promenimo ga tako da možemo da evidentiramo kada se ovi događaji dešavaju toDrag čvoru:

<!DOCTYPE html>
<html lang="en">
   <head>
      <title>Example #2: a draggable object with events attached </title>
      <script type="text/javascript" src="../../shared/js/sprintf.js"></script>
      <script type="text/javascript" src="../../shared/js/EventHelpers.js"></script>
      <script type="text/javascript" src="../../shared/js/DragDropHelpers.js"></script>

      <script type="text/javascript" src="js/02-dragObjectWithEvent.js"></script>

      <link rel="stylesheet" type="text/css" media="screen" href="css/test1.css" />

   </head>
   <body>

      <h1>Example #2: a draggable object with events attached</h1>

      <p>Try to drag the red box around.  This page will tell you if the
      object is currently dragging, and will log all <code>dragstart</code>
      and <code>dragstop</code> events</p>

      <p><strong>Is dragging:</strong> <span id="dragEventNotice">no</span>

      <p id="eventLog"><strong>Event now firing:</strong><br /><span id="eventNotice"></span>

      <a class="goBack" href="http://www.useragentman.com/blog/2010/01/10/cross-browser-html5-drag-and-drop/">Go back to the User Agent Man HTML5 Drag and Drop article</a></p>

      <a href="#" id="toDrag" draggable="true">This is a draggable item</a>

   </body>
</html>

U skripti 02-dragObjectWithEvent.js, čvor dragEventNotice koristiće se za izveštavanje da li korisnik povlači objekat, a eventNotice koristiće se za evidentiranje koje je događaje korisnik pokrenuo:

var dragObject = new function () {
   var me = this;

   var dragNode;
   var eventNoticeNode, dragEventNoticeNode;

   /* runs when the page is loaded */
   me.init = function () {

      if (EventHelpers.hasPageLoadHappened(arguments)) {
         return;
      }   

      /* The node being dragged */
      dragNode=document.getElementById('toDrag');

      /* The nodes that report to the user what is happening to that node*/
      eventNoticeNode = document.getElementById('eventNotice');
      dragEventNoticeNode = document.getElementById('dragEventNotice');

      /* The drag event handlers */
      EventHelpers.addEvent(dragNode, 'dragstart', dragStartEvent);
      EventHelpers.addEvent(dragNode, 'drag', dragEvent);
      EventHelpers.addEvent(dragNode, 'dragend', dragEndEvent);
   }

   /*
    * The dragstart event handler logs to the user when the event started.
    */
   function dragStartEvent(e) {
      eventNoticeNode.innerHTML =
         sprintf("<strong>%s</strong>: Drag Event started.<br />%s",
            new Date(),  eventNoticeNode.innerHTML);
   }

   /*
    * The drag event reports to the user that dragging is on.
    */
   function dragEvent(e) {
      dragEventNoticeNode.innerHTML = "Currently dragging.";
   }

   /*
    * The dragend event logs to the user when the event had finished *and*
    * also reports that dragging has now stopped.
    */
   function dragEndEvent(e) {
      eventNoticeNode.innerHTML =
         sprintf("<strong>%s</strong>: Drag Event stopped.<br />%s",
            new Date(), eventNoticeNode.innerHTML);
      dragEventNoticeNode.innerHTML = "Dragging stopped."
   }
}

// fixes visual cues in IE and Chrome.
DragDropHelpers.fixVisualCues=true;

EventHelpers.addPageLoadEvent('dragObject.init');

Pre nego što dođemo do drag događaja, koja reč o konvencijama kodiranja koje koristim za ovaj i sve druge primere koda u ovom članku:

  • linija var me = this stara se da skripta ne meša this ključnu reč objekta sa this ključnom reči unutar rukovaoca događaja
  • EventHelpers.addPageLoadEvent() (koja je deo EventHelpers.js) izvršiće dragObject.init kada je HTML učitan. addPageLoadEvent() zasnovana je na kodu iz članka The window.onload Problem - Solved! Deana Edwardsa. Sličan je jQuery-jevom $(document).ready() metodu i prototipovom dom:loaded događaju.
  • Ovaj kod koristi JavaScript verziju sprintf() koji sam pomenuo u prethodnoj blog objavi. Iskoristio sam ga u svim preostalim primerima, jer smatram da znatno olakšava čitanje koda (međutim, nije neophodan da bi HTML5 drag and drop funkcionisao).

Vratimo se na drag događaje - primetićete da sam podesio dragstart, drag i dragend događaje. Kada se ova stranica učita, reći će vam da li se objekat trenutno prevlači i prijaviće sve dragstart i dragstop događaje na ekranu. Kliknite na primer linka ispod i vidite sami:

See Example #2, a draggable object with events attached

Korak 3: Podešavanje događaja na ciljnom objektu

Korak 2 pokazao nam je kako možemo da prevlačimo objekat po ekranu i da znamo kada je svako prevlačenje počelo i kad se završilo. Sada treba da spustimo objekat. Hajde da definišemo da je drop cilj objekat gde objekat koji se može prevlačiti može da se spustiti. Na drop cilj mogu da se pripoje sledeći rukovaoci događaja:

Događaj Opis
dragenter Pokreće se kada je objekat koji se može prevlačiti prvi put prevučen u neki objekat.
dragover Pokreće se svaki put kada je objekat koji se može prevlačiti pomeren u nekom objektu. Napomena: ako želite da objekat bude spušten u ovaj objekat, morate da otkažete default ponašanje ovog rukovaoca događajem.
dragleave Pokreće se kad je objekat koji se može prevlačiti izvučen iz objekta.
drop Pokreće se kada je objekat koji se može premeštati spušten u neki objekat.

Ako želite da objekat postane drop cilj, morate mu pripojiti kako dragover i drop događaje. Možete pitati: "Razumem zašto moram da implementiram drop (ipak je to jedna polovina termina "drag and drop"), ali zašto mi treba dragoverKao što je navedeno u tabeli gore, događaj drop neće se pokrenuti osim ako ne otkažete default ponašanje događaja cilja dragover event. Ova stavka je veoma važna i vaše skripte neće ispravno raditi ako ovo ne uradite.

Možete pitati: “Zašto uopšte postoji dragover događaj? Zar nije drag dovoljan?" (U svom članku, Peter-Paul Koch postavio je ovo pitanje malo naglašenije od mene). dragover događaja zaista sadrži neke korisne informacije, kao što su koordinate strelice miša unutar ciljnog događaja, (Kada sam kreirao DragDropHelpers, ove informacije omogućile su mi da sredim Explorer-ov i Chrome-ov problem sa vizuelnim signalom koji sam pomenuo u koraku #1). Ove koordinate uskladištene su u svojstvima offsetX i offsetY, osim u Firefox 3.5 Radi pogodnosti, DragDropHelpers uključuje metod zvani getEventCoords() koji će koristiti odgovarajuće svojstvo da bi dobio ove vrednosti. (Napomena: layerX i layerY precizni su samo kad je CSS position svojstvo ciljnog dropa podešeno na relative, absolute ili fixed).

bismo pokazali kako da svom kodu dodate drop funkcionalnost, uzmimo kod iz primera #2 i dodajmo ciljni drop:

<!DOCTYPE html>
<html lang="en">
   <head>
      <title>Example #3: a dragable object with a drop target</title>
      <script type="text/javascript" src="../../shared/js/sprintf.js"></script>
      <script type="text/javascript" src="../../shared/js/EventHelpers.js"></script>
      <script type="text/javascript" src="../../shared/js/DragDropHelpers.js"></script>

      <script type="text/javascript" src="js/03-dragObjectWithTargetObject.js"></script>

      <link rel="stylesheet" type="text/css" media="screen" href="css/test1.css" />

   </head>
   <body>

      <h1>Example #3: a dragable object with a drop target</h1>

      <p>Try to drag the red box around and dropping it in the target object.</p>

      <p><strong>Is dragging:</strong> <span id="dragEventNotice">no</span>

      <p id="eventLog"><strong>Event now firing:</strong><br /><span id="eventNotice"></span>
      <a class="goBack" href="http://www.useragentman.com/blog/2010/01/10/cross-browser-html5-drag-and-drop/">Go back to the User Agent Man HTML5 Drag and Drop article</a></p>

      <a href="#" id="toDrag" draggable="true">This is a draggable item</a>
      <div id="dropTarget">This is a "target" object</div>

   </body>
</html>

Ciljni drop biće <div> sa id of dropTarget. Skripta 03-dragObjectWithTargetObject.js isti je kod kao u primeru #2, osim što dodajemo dva rukovalaca događajem:

  • dragover događaj koji izveštava gde je miš u dropTarget
  • drop događaj koji izveštava kada se desio drop
var dragObject = new function () {
   var me = this;

   var dragNode, targetNode;
   var eventNoticeNode, dragEventNoticeNode;
   me.init = function () {

   	if (EventHelpers.hasPageLoadHappened(arguments)) {
   		return;
   	}	

   	dragNode=document.getElementById('toDrag');
   	targetNode=document.getElementById('dropTarget');
   	eventNoticeNode = document.getElementById('eventNotice');
   	dragEventNoticeNode = document.getElementById('dragEventNotice');

   	/* These are events for the draggable object */
   	EventHelpers.addEvent(dragNode, 'dragstart', dragStartEvent);
   	EventHelpers.addEvent(dragNode, 'drag', dragEvent);
   	EventHelpers.addEvent(dragNode, 'dragend', dragEndEvent);

   	/* These are events for the object to be dropped */
   	EventHelpers.addEvent(targetNode, 'dragover', dragOverEvent);
   	EventHelpers.addEvent(targetNode, 'drop', dropEvent);

   }

   function dragStartEvent(e) {
   	showMessage("Drag Event started");
   }

   function dragEvent(e) {
   	dragEventNoticeNode.innerHTML = "Currently dragging.<br />";
   }

   function dragEndEvent(e) {
   	showMessage("Drag Event stopped");
   	dragEventNoticeNode.innerHTML = "Dragging stopped."
   }

   function dragOverEvent(e) {
   	var coords = DragDropHelpers.getEventCoords(e);
   	showMessage(sprintf(
   	   "Drag over event happened on node with id %s at coordinate (%d, %d)",
   	   this.id, coords.x, coords.y));
   	EventHelpers.preventDefault(e);
   }

   function dropEvent(e) {
   	showMessage("Drop event happened on node with id " + this.id);
   	EventHelpers.preventDefault(e);
   }

   function showMessage(message) {
   	eventNoticeNode.innerHTML =
   		sprintf("<strong>%s</strong>: %s<br />%s",
   			new Date(), message, eventNoticeNode.innerHTML);
   }

}

// fixes visual cues in IE and Chrome.
DragDropHelpers.fixVisualCues=true;

EventHelpers.addPageLoadEvent('dragObject.init');

(Napomena: EventHelpers.preventDefault(e) radi ono što biste očekivali: sprečava default ponašanje rukovaoca događajem, slično prototipovoj Event.stop() ili jQuery-jevoj event.preventDefault())

See Example #3: a dragable object with a drop target

Imajte na umu da, u gorenavedenom primeru, vreme kada se pokrene dragend događaj objekta koji se može prevlačiti i vreme kada se pokrene drop događaj ciljanog drag-a nije konzistentno među pretraživačima. Safari i Chrome prvo pokreću dragend, dok Firefox i Internet Explorer najpre pokreću drop.

Druga dva drop cilj događaja dragenter i dragleave nisu apsolutno neophodna za svaku skriptu, ali hajde da ipak dodamo ove događaje primeru #3 da vidimo kako oni rade:

var dragObject = new function () {
   var me = this;

   var dragNode, targetNode;
   var eventNoticeNode, dragEventNoticeNode;
   me.init = function () {

      if (EventHelpers.hasPageLoadHappened(arguments)) {
         return;
      }   

      dragNode=document.getElementById('toDrag');
      targetNode=document.getElementById('dropTarget');
      eventNoticeNode = document.getElementById('eventNotice');
      dragEventNoticeNode = document.getElementById('dragEventNotice');

      /* These are events for the draggable object */
      EventHelpers.addEvent(dragNode, 'dragstart', dragStartEvent);
      EventHelpers.addEvent(dragNode, 'drag', dragEvent);
      EventHelpers.addEvent(dragNode, 'dragend', dragEndEvent);

      /* These are events for the object to be dropped */
      EventHelpers.addEvent(targetNode, 'dragover', dragOverEvent);
      EventHelpers.addEvent(targetNode, 'drop', dropEvent);
      EventHelpers.addEvent(targetNode, 'dragenter', dragEnterEvent);
      EventHelpers.addEvent(targetNode, 'dragleave', dragLeaveEvent);
   }

   function dragStartEvent(e) {
      showMessage("Drag Event started");
   }

   function dragEvent(e) {
      dragEventNoticeNode.innerHTML = "Currently dragging.<br />";
   }

   function dragEndEvent(e) {
      showMessage("Drag Event stopped");
      dragEventNoticeNode.innerHTML = "Dragging stopped.";
   }

   function dragOverEvent(e) {
      var coords = DragDropHelpers.getEventCoords(e);

      showMessage(sprintf(
         "Drag over event happened on node with id %s at coordinate (%d, %d)",
         this.id, coords.x, coords.y));

      EventHelpers.preventDefault(e);
   }

   function dropEvent(e) {
      showMessage("Drop event happened on node with id " + this.id);
      EventHelpers.preventDefault(e);

   }

   function dragEnterEvent(e) {
      showMessage("Drag Enter event happened on node with id " + this.id);
   }

   function dragLeaveEvent(e) {
      showMessage("Drag Leave event happened on node with id " + this.id);
   }

   function showMessage(message) {
      eventNoticeNode.innerHTML =
         sprintf("<strong>%s</strong>: %s<br />%s",
            new Date(), message, eventNoticeNode.innerHTML);
   }

}

// fixes visual cues in IE and Chrome.
DragDropHelpers.fixVisualCues=true;

EventHelpers.addPageLoadEvent('dragObject.init');

See Example #3a: a drop target with dragenter and dragleave event handlers

(Imajte na umu da će se u Firefox 3.5 događaj dragleave pokrenuti neposredno pre događaja drop, dok drugi pretraživači ne pokreću dragleave pri spuštanju.)

Korak 4: Razmena podataka između objekata koji se mogu prevlačiti i ciljnih objekata

Kada se objekat koji se može prevlačiti spusti na drop cilj, ta dva objekta mogu da prosleđuju informacije među sobom koristeći svojstvo dataTransfer drop događaja. Ovo svojstvo ima dve metode , setData() i getData(). Da bi prosledio podatke između njih, programer mora da koristi setData() tokom jednog od drag događaja čvora koji se može prevlačiti (npr. dragstart). Drop cilj tada može da primi ove podatke tokom jednog od svojih događaja koristeći metod getData().

Metod Opis
setData(dataType, data)

Podešava deljive podatke između čvora koji se može prevlačiti i drag cilja.

Parametri

  • dataType- Tip podataka koji treba treba da se podesi. Za sada, jedini cross-pretraživač tipovi podataka koji mogu da se podese jesu ‘Text’ i ‘Url’.
  • data – Podaci koji će se podesiti.
getData(dataType)

Uzima podatke koji su prethodno podešeni od strane dataTransfer.setData(). Takođe može da uzme podatke koji su podešeni dataTransfer.setData() pozivom s neke druge stranice, prozora drugog pretraživača, pretraživača nekog drugog proizvođača na istoj mašini. Isto može da uzme podatke koji su podešeni od strane drop događaja neke druge desktop aplikacije (npr. Windows WordPad)./p>

Parametri

  • dataType – Tip podataka koji treba da se podese. Za sada, jedini cross-pretraživač tipovi podataka koji mogu da se podese jesu ‘Text’ i ‘Url’.

setData() uzima jedan string parametar, koji je podatak koji želite da podelite između objekta koji se može prevlačiti i drop cilja.

Hajde da uzmemo kod iz koraka #3 i da ga malo promešamo: umesto da imamo jedan objekat koji se može prevlačiti, hajde da napravimo četiri. Uzeo sam četiri slike Bitlsa iz Wikmedia Commons i učinio ih mogućim za prevlačenje.

<!DOCTYPE html>
<html lang="en">
   <head>
      <title>Test 4: setData() and getData()</title>
      <script type="text/javascript" src="../../shared/js/sprintf.js"></script>
      <script type="text/javascript" src="../../shared/js/helpers.js"></script>
      <script type="text/javascript" src="../../shared/js/DragDropHelpers.js"></script>

      <script type="text/javascript" src="js/04-setDataGetData.js"></script>

      <link rel="stylesheet" type="text/css" media="screen" href="css/test1.css" />

   </head>
   <body>

      <h1>Test 4: <code>setData()</code> and <code>getData()</code></h1>

      <img draggable="true" src="images/george.png" alt="George Harrison" />
      <img draggable="true" src="images/john.png" alt="John Lennon" />
      <img draggable="true" src="images/paul.png" alt="Paul McCartney" />
      <img draggable="true" src="images/ringo.png" alt="Ringo Starr" />

      <div id="dropTarget"><span>Drop an image here to find out more information about it.</span></div>

   </body>
</html>

Skripta 04-setDataGetData.js koristi dataTransfer.setData() i .getData() da kopira <img> Bitlsa koja se prevlači, i postavlja sadržaj atributa alt ispod slike.

var dragObject = new function () {
   var me = this;

   var targetNode;
   var eventNoticeNode, dragEventNoticeNode;

   var dataTransferCommentString;

   me.init = function () {

      if (EventHelpers.hasPageLoadHappened(arguments)) {
         return;
      }   

      targetNode=document.getElementById('dropTarget');
      eventNoticeNode = document.getElementById('eventNotice');
      dragEventNoticeNode = document.getElementById('dragEventNotice');

      /* These are events for the draggable objects */
      var dragNodes = cssQuery('[draggable=true]');
      for (var i = 0; i < dragNodes.length; i++) {
         var  dragNode=dragNodes[i]
         EventHelpers.addEvent(dragNode, 'dragstart', dragStartEvent);
      }

      /* These are events for the object to be dropped */
      EventHelpers.addEvent(targetNode, 'dragover', dragOverEvent);
      EventHelpers.addEvent(targetNode, 'drop', dropEvent);
   }

   function dragStartEvent(e) {
      e.dataTransfer.setData('Text',
         sprintf('<img src="%s" alt="%s" /><br /><p class="caption">%s</p>',
            this.src, this.alt, this.alt
         )
      );
   }

   function dragOverEvent(e) {
      EventHelpers.preventDefault(e);
   }

   function dropEvent(e) {
      this.innerHTML = e.dataTransfer.getData('Text');
      EventHelpers.preventDefault(e);
   }

}

// fixes visual cues in IE and Chrome.
DragDropHelpers.fixVisualCues=true;

EventHelpers.addPageLoadEvent('dragObject.init');

See Example #4: using setData() and getData()

Kao što je ranije pomenuto, podaci koji su podešeni u setData() mogu se pročitati i u drugim aplikacijama. Ako ste u operativnom sistemu Windows, otvorite WordPad i prevucite jednu od slika u njega. Videćete podatke koji su dati setData() tokom događaja dragstart.

Ova interoperabilnost sa operativnim sistemom omogući će programerima da rade neke veoma zanimljive stvari koje su van opsega ovog članka. Ja ću, međutim, želeti da istražim ovo u budućim postovima.

Korak 5: Drag and drop efekti

U tradicionalnoj desktop aplikaciji, možete koristiti drag and drop da kopirate objekte, da premeštate objekte i da kreirate veze sa stvarima. Možete da koristite i drag and drop HTML5 događaje da biste to uradili, ali bilo bi lepo da se korisniku daju vizuelni signali radi pokazivanja šta sve objekti koji se mogu prevlačiti mogu da urade, i kakve sve akcije drop cilja mogu da prihvate. Takođe bi bilo fino da se dozvoli samo "copy objects" da samo spuštaju na drop ciljeve koji su programirani da ih prihvate.

Ove osobine mogu se implementirati koristeći e.dataTransfer.effectAllowed na objekat koji se može prevlačiti i e.dataTransfer.dropEffect() na drop cilj:

Metod Opis
e.dataTransfer.effectAllowed Podešava tip efekta koji je objektu koji se može prevlačiti dozvoljeno da napravi.Validne vrednosti su copy, move, link, copyMove, copyLink, linkMove, all, i none.
e.dataTransfer.dropEffect Podešava tip efekta koji je drop cilju dozvoljeno da primi. Validni efekti podrazumevaju copy, move, i link.

Kada su ovi pravilno podešeni, drag and drop će funkcionisati samo kada su effectAllowed objekta koji se može prevlačiti i dropEffect drop cilja kompatibilni. Da bismo ilustrovali ovo, hajde da prvo promenimo dragstart i dragover događaje u primeru #4 da bismo izveli copy efekat.

   function dragStartEvent(e) {
      e.dataTransfer.effectAllowed="copy"; 

      e.dataTransfer.setData('Text',
         sprintf('<img src="%s" alt="%s" /><br /><p class="caption">%s</p>',
            this.src, this.alt, this.alt
         )
      );
   }

   function dragOverEvent(e) {
      e.dataTransfer.dropEffect = "copy";
      EventHelpers.preventDefault(e);
   }

Example #5: matching drag and drop effects

Primetite da se, za razliku od primera #4, strelica miša promenila iz ikone kopiranja (u operativnom sistemu Windows) [Windows Copy Icon]) u ikonu pokretanja ([Windows Move Icon]).

Sada hajde da promenimo kod tako da objekat koji se može prevlačiti i drop cilj nemaju podudarne efekte:

   function dragStartEvent(e) {
      e.dataTransfer.effectAllowed="copy"; 

      e.dataTransfer.setData('Text',
         sprintf('<img src="%s" alt="%s" /><br /><p class="caption">%s</p>',
            this.src, this.alt, this.alt
         )
      );
   }

   function dragOverEvent(e) {
      e.dataTransfer.dropEffect = "move";
      EventHelpers.preventDefault(e);
   }

See Example #5a: unmatching drag and drop effects

Videćete da korisnik nije u stanju da spusti sliku na drop cilj. .

Update (28. januar 2010): Postoji dokumentovani bug u Safari-ju i Chrome-u za Windows, gde se drag and drop neće desiti ako su effectAllowed i dropEffect podešeni na move. Ovaj bug ne dešava se u Mac izdanjima ovih pretraživača.

Složeniji primer

Uzmimo sve informacije koje smo sakupili i napravimo scenario u "realnom svetu". Recimo da su od vas tražili da kreirate administrativni programčić o “pravima korisnika” za neki web site. Taj programčić imaće tri kategorije ovlašćenja: "neraspoređeni korisnici", "ograničeni korisnici" (npr. korisnici koji imaju ograničen pristup site-u) i "ovlašćeni korisnici" (npr. korisnici koji će imati administrativni pristup site-u):

Screenshot of the user entitlement screen we want to implement

Screenshot of the user entitlement screen we want to implement

Administratoru bi trebalo da bude prilično lako da drag and drop korisnike tamo-amo između različitih skupina ovlašćenja, s ciljem da podigne ili snizi ovlašćenja korisnika.

Hajdemo kroz korake koje smo gore utvrdili da bismo sproveli ovaj scenario.

Step 1: Defining a Draggable Objects

Prvo hajde da pogledamo tabelu u HTML:

<table>
    <thead>
        <tr>
            <th>Unassigned Users</th>
            <th>Restricted Users</th>
            <th>Power Users</th>
        </tr>
    </thead>

    <tbody>
        <tr>
            <td id="unassignedUsers">
            	<a href="#" draggable="true">Moe Howard</a>
		<a href="#" draggable="true">Curly Howard</a>
		<a href="#" draggable="true">Shemp Howard</a>
            	<a href="#" draggable="true">Larry Fine</a>
	    </td>

            <td id="restrictedUsers">
            </td>

            <td id="powerUsers">
            </td>
        </tr>
    </tbody>
    <tfoot>
	<td id="unassignedUsersHelp">
	  Drag a user from this list to another list to change the
	  user's permissions.
	</td>
	<td id="restrictedUsersHelp">
	  Dragging user here will give this user restricted
	  permissions.
	</td>
	<td id="powerUsersHelp" >
	  Dragging a user here will give this user power user access.
	</td>
    </tfoot>
</table>

Imajte na umu da je sadržaj u tfoot čvoru skriven od strane CSS.

Korak 2: Podešavanje događaja na objektima koji se mogu prevlačiti

"Korisnici" su objekti koji se mogu prevlačiti. U skripti, pronalazimo sve ove čvorove koristeći Dean Edwards-ov cssQuery i podesimo dragstart i dragend rukovaoce događaja na svaki od njih (Napomena: cssQuery omogućava nam da odaberemo čvorove CSS selektorom, slično ugrađenoj funkcionalnosti unutar jQuery-ja).

userNodes = cssQuery('[draggable=true]');
for (var i=0; i<userNodes.length; i++) {
   EventHelpers.addEvent(userNodes[i], 'dragstart', userDragStartEvent);
   EventHelpers.addEvent(userNodes[i], 'dragend', userDragEndEvent);
}

Onda prati aktuelni čvor koji se prevlači u objektnu promenljivu currentlyDraggedNode i čini taj čvor transparentnim tako što ga čini članom draggedUser klase, koju stylesheet definiše kao transparentan.

function userDragStartEvent(e) {
   currentlyDraggedNode = this;
   currentlyDraggedNode.className = 'draggedUser';
}

dragend događaj uklanja transparentnost currentlyDraggedNode:

function userDragEndEvent(e) {
   currentlyDraggedNode.className = '';
}

Korak 3: Podešavanje događaja na ciljne objekte

Sve ćelije tabele u <tbody> jesu drop ciljevi, tako da ponovo koristimo cssQuery da bismo zgrabili sve te čvorove i podesili odgovarajuće događaje.

userListNodes = cssQuery('.userList');

for (var i=0; i<userListNodes.length; i++) {
   var userListNode = userListNodes[i];
   EventHelpers.addEvent(userListNode, 'dragover', cancel);
   EventHelpers.addEvent(userListNode, 'dragleave', userDragLeaveListEvent);
   EventHelpers.addEvent(userListNode, 'drop', userDropListEvent);
   EventHelpers.addEvent(userListNode, 'dragenter', userDragOverListEvent);
}

Najvažniji događaj je drop rukovalac događajem. Ovaj rukovalac događajem uzima currentlyDraggedNode, uklanja ga iz trenutne ćelije tabele (koristeći removeChild()) i spušta ga na drop cilj (koristeći appendChild()).

function setHelpVisibility(node, isVisible) {
   var helpNodeId = node.id + "Help";
   var helpNode = document.getElementById(helpNodeId);

   if (isVisible) {
      helpNode.className =  'showHelp';
   } else {
      helpNode.className =  '';
   }
}

function userDropListEvent(e) {
   currentlyDraggedNode.parentNode.removeChild(currentlyDraggedNode);
   this.appendChild(currentlyDraggedNode);
   setHelpVisibility(this, false);
   userDragEndEvent(e);
}

Pokazuju sadržaj ćelije tabele ispod tako što je čine članom klase showHelp. Takođe

sprečavaju dragover default ponašanje, tako da će se drop događaji pokrenuti u drop cilju.

function userDragOverListEvent(e) {
   setHelpVisibility(this, true);
   EventHelpers.preventDefault(e);
}

dragleave ponovo sakriva sadržaj ćelije tabele ispod.

function userDragLeaveListEvent(e) {
  setHelpVisibility(this, false);
}
function userDragEndEvent(e) {
   currentlyDraggedNode.className = '';
}

Koraci 4 i 5

Mi zapravo ne razmenjujemo nikakve podatke između objekta koji se može prevlačiti i drag cilja - kada se desi drop događaj, objekat koji se može prevlačiti premešta se iz jedne kolone u drugu, bez potrebe za bilo kakvim dataTransfer podacima. Drag and drop efekti već su obrađeni u koraku 3, tako da smo gotovi.

See the above example in action

Drag and Drop između okvira

Korišćenjem gorenavedenih informacija, takođe je moguće da drag and drop objekte između okvira (to je nešto što mislim da nijedna old school drag and drop skripta ne može da uradi). Napravio sam ispod primer, i ostavljam ga kao vežbu za čitaoca da prođe kroz kod i sazna kako on funkcioniše.

See the inter-frame drag and drop example in action

Sva problemi na koje sam naišao vezani za razne pretraživače

Ovo su svi problemi i pitanja vezana za razne pretraživače koja sam do sada saznao. Ažuriraću ovaj spisak kako na koja nova budem nalazio.

  • U Firefox 3.5, dragleave događaj pokrenuće se baš pre drop događaja, koji ostali pretraživači ne pokreću dragleave prilikom spuštanja.
  • Vreme kaada se pokrene dragend događaj objekta koji se može prevlačiti i vreme koje pokreće drop događaj drag cilja nije konzistentno među pretraživačima. Safari i Chrome prvo pokreću dragend, dok Firefox i Internet Explorer prvo pokreću drop.
  • Prema Francisku Tolmasky-ju, setData() ne može se pozvati tokom drag događaja u Firefox-u. Nadam se da će ovo biti popravljeno u budućem izdanju pretraživača.
  • DragDropHelpers implementira rešenje za Explorer tako da može da prevlači bilo koji objekat. Međutim, nisam upotrebio ovu funkcionalnost ni u jednom od primera, jer izlaže problem da Safari nije u stanju da ispravno prevuče objekat kada korisnik pokrene prevlačenje na tekstu u njemu. Ovaj problem ne dešava se sa tekstom unutar linkova, već sa tekstom unutar drugih čvorova (kao <div> tag). Saznao sam da je ovaj problem rešen u Safarijevim noćnim verzijama, tako da sam ostavio funkcionalnost u DragDropHelpers biblioteci za buduću upotrebu.
  • DragDropHelpers.js "popravlja" vizuelne signale prevlačenja u Explorer-u i Chrome-u tako što:
    • pravi transparentan i apsolutno pozicioniran klon objekta koji se može prevlačiti kada se pokrene njegov dragstart događaj
    • pomera klonirani objekat blizu miša kada je pokrenut <body>-jev drag događaj.
    • uništava klon kada se pokrene dragend događaj objekta koji se može prevlačiti

    Nisam ga uključio po default-u jer nisam siguran kada (i da li) će ovaj problem biti rešen u Internet Exploreru ili Chrome-u. Takođe ne radi dobro pri prevlačenju objekata iz jednog prozora ili okvira u drugi, budući da klonirani vizuelni signal ne može da skače sa stranice na stranicu. Slobodno ga koristite (ili ne) onako kako vam odgovara.

  • Kao što je pomenuto ranije, zbog buga u Webkit, drag and drop neće se desiti ako su effectAllowed i dropEffect podešeni na move u Safari 4.0.4. Nadam se da će ovo biti popravljeno u budućoj verziji WebKit-a.

Zaključak

HTML5 drag and drop biće važan deo našeg front-end programskog alata. Čak i ako su ukusi koje prodavci pretraživača danas nude nepotpuni, moguće je da se ove greške izglade tako da se stvore korisne web aplikacije. Ono što sam ovde pokrio samo malo grebe po površini - ima još svojstava i metoda koje neki pretraživači nude i koji mogu da se iskoriste za dodatno poboljšanje drag and drop iskustva. Ali, predugo sam radio na ovom blog unosu, a supruga me već gleda u fazonu "zašto si budan u ovo doba", tako da mislim da ću se ovde zaustaviti za sada.

Preuzimanje

DragDropHelpers, i sve kodove koji su upotrebljeni u ovom članku možete preuzeti ispod.

Download the Latest Version of DragDropHelpers.js from GitHub.

Published (Last edited): 09-04-2013 , source: http://www.useragentman.com/blog/2010/01/10/cross-browser-html5-drag-and-drop/