In questo articolo mostrerò quanto è facile implementare le funzionalità di drag'n'drop utilizzando le nuove interfacce che HTML5 mette a disposizione degli sviluppatori Javascript.
Dato che HTML5 non è ancora uno standard non tutti i browsers lo supportano, e tra li stessi che ne implementano le specifiche non tutti le supportano totalmente.
Dimostrerò in questo articolo, testato con FireFox Beta 4.04 e Chrome v.7, che seppur entrambi i browsers implementino i gestori degli eventi scatenati durante le varie fasi del drag'n'drop, solo FF implementa correttamente l'interfaccia DataTransfer, che fondamentalmente è una hashmap associata agli eventi, per poter trasportare informazioni supplementari all'oggetto trascinato e depositato altrove all'interno della pagina.
Questo è il motivo per il quale l'articolo originale da cui ho preso spunto, come dichiarato dallo stesso publisher, funziona correttamente solo su FF e non su Chrome.
Nel caso non si abbia molta dimestichezza con il drag'n'drop e le API da utilizzare è assolutamente consigliata la lettura del suddetto articolo.
Successivamente mostrerò come ho dovuto procedere per aggirare il problema.
Nota:
L'esempio consiste nel poter creare la propria formazione della nazionale Italiana di calcio trascinando le immagini dei giocatori dalla panchina al campo di gioco, che è diviso in 4 zone: area di porta, difesa, centrocampo ed attacco.
Per semplificare la comprensione dell'articolo e focalizzare l'attenzione solo sulla gestione degli eventi, che vengono scatenati durante le varie fasi(click, trascinamento, rilascio ecc..), ho ridotto gran parte del codice originale grazie all'utilizzo degli Standard Event Attribute che alcuni elementi HTML posseggono. In aggiunta all'originale, oltre ad una migliore veste grafica e la possibilità di poter scegliere tra molti dei campioni della nazionale Italiana campione del Mondo, è presente l'implementazione di una banale classe per wrappare il suddetto elemento DataTransfer e rendere lo script cross-browsers e sono gestiti con alert i posizionamenti errati dei giocatori(es: difensore al posto di un attacccante). Detto questo, rimane pur sempre un prototipo valido per una buona introduzione alla gestione del drag'n'drop con le nuove API HTML5.
Tutto il codice sorgente presente in questo articolo è comodamente scaricabile in formato .zip a questo link.
Il codice HTML
Analizzando bene il codice HTML sarà possibile scoprire molti meccanismi che vengono utilizzati successivamente dalla parte Javascript per maneggiare le operazioni di drag'n'drop ed eseguire controlli sulla posizione in campo dei vari giocatori.
Il BODY è composto da due DIV: la panchina ed il campo da gioco.
All'interno della panchina sono presenti tutte le immagini dei giocatori, mentre il campo è composto a sua volta da 4 DIV, uno per ogni zona del campo sopra elencata.
Di seguito un estratto del DIV#panchina:
<div id="panchina" ondragenter="return onDrgEntr(event);"
ondragover="return onDrgOvr(event);" ondrop="return onDrp(event);">
<img src="italia.png" draggable="false" class="no-drag">
<img src="buffon.jpg" id="Buffon" title="Buffon" class="goalkeeper"
ondragstart="return onDrgStrt(this,event);">
<img src="amelia.jpg" id="Amelia" title="Amelia" class="goalkeeper"
ondragstart="return onDrgStrt(this,event);">
<img src="peruzzi.jpg" id="Peruzzi" title="Peruzzi" class="goalkeeper"
ondragstart="return onDrgStrt(this,event);">
..................................
</div>
Come è possibile notare sul DIV#panchina sono presenti gli event attributes per poter richiamare i gestori degli eventi dragenter, dragover e drop, i quali ho settato rispettivamente con i metodi javascript onDrgEntr(event), onDrgOvr(event) e onDrp(event).
Su ogni immagine è presente, oltre all'attributo per la gestione delle operazioni di trascinamento soddisfate grazie al metodo onDrgStrt(elemento,evento), il riferimento alla classe CSS per disegnare il componente: attenzione, la classe è solo un espediente per poter successivamente controllare se la posizione scelta per il giocatore in campo è la più indicata.
Ogni portiere ha la classe CSS goalkeeper, i difensori defender, i centrocampisti middlefield e gli attaccanti striker.
Ora qualcosa riguardo il campo da gioco, ovvero il DIV#field, di cui segue il codice HTML:
<div id="field"
ondragenter="return onDrgEntr(event)"
ondragover="return onDrgOvr(event);"
ondrop="return onDrp(event);">
<div id="field-goalkeeper" class="zone"></div>
<div id="field-defender" class="zone"></div>
<div id="field-middlefield" class="zone"></div>
<div id="field-striker" class="zone"></div>
</div>
Concettualmente il DIV#field deve comportarsi come il DIV#panchina: deve poter accogliere dinamicamente nuovi elementi, o child, e deve potersene anche privare; quindi sono presenti li stessi event attributes già visti per la panchina.
In questo articolo è tralasciata la definizione degli stili CSS, che sono presenti all'interno del file .zip scaricabile.
Prima di analizzare il codice Javascript che è stato associato ad ogni event attribute degli elementi HTML sopra trattati è necessario conoscere la wrapper class DataTransfer in quanto utilizzata dagli event handlers di cui si parlerà.
La wrapper class DataTransfer
Per poter aggirare la mancanza del supporto all'interfaccia event.dataTransfer dell'attuale versione 7 di Chrome ho creato un oggettino che espone i metodi base dell'interfaccia originale e che ha lo stesso modo di utilizzo. Questo oggetto ha uno scope globale essendo dichiarato al massimo livello nella pagina.
Segue il codice sorgente del mio wrapper:
/**
* Wrapper class per risolvere la mancanza
* di supporto all'oggetto event.dataTransfer
* caratteristica del browser Chrome v7.
* Si tratta un array da utilizzare come una HashMap
* tramite coppie di chiave/valore.
*/
function DataTransfer(){
var m = new Array();
this.getData = function(key){
return m[key];
};
this.setData = function(key, value){
m[key] = value;
};
}
var dataTransfer = new DataTransfer();
Si tratta proprio di un banale oggettino che si comporta come una hashmap: attribuisce ad una chiave un valore, che restituisce solo con la chiave abbinata.
Ora il codice Javascript implementato per la gestione degli eventi e la realizzazione delle funzionalità di base che il drag'n'drop richiede.
Gestire gli eventi dragEnter & dragOver
Questi eventi si verificano quando il mouse si trova su un componente che è pronto ad accettare nuovi elementi (evento dragenter).Nel momento in cui avviene il drop questo evento muore e si scatena dragover, che determina il risultato da restituire all'utente: se l'evento non viene interrotto o cancellato solitamente questo non fa niente di particolare.
/**
* Gestore dell'evento dragenter sul target.
* Per evitare che il drop avvenga in altre zone della pagina
* attraversate dal mouse durante il trascinamento di un'oggetto
* viene eseguita una chiamata al metodo event.preventDefault().
*/
function onDrgEntr(event) {
event.preventDefault();
return true;
}
/**
* Gestore dell'evento dragover sul target.
*/
function onDrgOvr(event){
return false;
}
Gestire l'evento dragStart
Questo evento si scatena dopo aver cliccato su un componente della pagina per poterlo trascinare mantenendo il bottone del mouse premuto. Si può affermare che è l'evento che da il via al drag'n'drop.
/**
* Gestore dell'evento ondragstart sull'oggetto trascinato.
* Riceve il riferimento all'oggetto trascinato
* per poter settare dei valori utili all'applicazione.
*/
function onDrgStrt(el,event){
dataTransfer.setData("player", el.getAttribute('title'));
dataTransfer.setData("role", el.getAttribute('class'));
return true;
}
Come si nota, la firma del metodo prevede un evento come secondo parametro del quale però all'interno del metodo stesso non viene fatto alcun utilizzo.
Questo è dovuto alla defezione di Chrome v.7 di cui sopra si è parlato: in questo caso, anziché utilizzare il mio wrapper, si sarebbe dovuto utilizzare l'istanza dell'oggetto dataTransfer associato all'evento. Il codice corretto sarebbe dovuto essere:
function onDrgStrt(el,event){
event.dataTransfer.setData("player", el.getAttribute('title'));
event.dataTransfer.setData("role", el.getAttribute('class'));
return true;
}
Rimane solo la gestione dell'ultima fase che conclude un drag'n'drop: il drop, o il rilascio del componente trascinato.
Gestire l'evento drop
Questo è l'evento che solitamente contiene la maggior parte della logica e dei controlli applicativi.
In questa circostanza, l'event handler associato è il metodo onDrp(event):
al suo interno sono effettuati controlli sia sui targets depositari dell'oggetto droppato, per evitare che venga depositato su un componente di gioco errato, sia sui componenti draggati, per esempio avvisare immediatamente l'utente di aver posizionato un difensore in attacco o centrocampo.
/**
* Gestore dell'evento ondrop sul target.
*/
function onDrp(event){
// Contiene il valore dell'attributo title, identico ad id,
// del giocatore draggato.
var val = dataTransfer.getData('player');
// Rappresenta il contenitore nel quale si è lasciato cadere
// il giocatore trascinato.
var trgt = event.target;
// Evito di depositare un giocatore su un'altro giocatore.
if(trgt.tagName.toLowerCase() == 'img'){
document.getElementById('panchina').appendChild(trgt);
dataTransfer.setData('player','');
return false;
}
// Controllo della zona, con eventuale avviso,
// e del riposizionamento in panchina.
if(trgt.id.index0f(document.getElementById(val).className) == -1
&& trgt.id != 'panchina')
alert("Il giocatore non ricopre il suo ruolo.");
// Deposito il giocatore in una porzione di campo.
trgt.appendChild(document.getElementById(val));
dataTransfer.setData('player','');
return false;
}
Come è facile intuire, implementare applicazioni client che fanno uso del drag'n'drop con HTML5 è molto più semplice, chiaro e rapido rispetto a ciò che fino a ieri si era costretti ad inventare; e se si considera che il nuovo linguaggio è ancora molto lontano dal divenire uno standard, è naturale provare la speranza che le cose potrebbero diventare ancore più semplici.
L'esempio proposto ha ampie possibilità di miglioramento, sia a livello di codice sia nel numero di funzionalità e controlli, per esempio non dovrebbe consentire di avere più di 11 giocatori schierati e dovrebbe vietare la presenza di 2 portieri nello stesso ruolo.
Alla prossima,
MA