JavascriptProva

lunedì 30 gennaio 2012

Dragdrop piuttosto elegante, tramite innesco di evento e passaggio di dati

Credo di aver ottenuto un ottimo risultato:
Questo è il codice della mia funzione, che ho ribattezzato Dragabile, avente per parametri gli id degli elementi HTML che si vogliano rendere suscettibili di trascinamento.
function Dragabile(){
 var nomi=new Array();
 var offsetX;
 var offsetY;
 var startX;
 var startY;
 var dragElement;
 for(n=0;n<arguments.length;n++)
  nomi[n]=arguments[n];
 
 var inizializza=function(){
  document.onmousedown=onMouseDown;
  document.onmouseup=onMouseUp; 
  document.onmouseover=onMouseOver;
 }
 var onMouseOver=function(e){
  for(n=0;n<nomi.length;n++)
   if(e.target.id==nomi[n]) e.target.style.cursor="pointer";
 }
 var onMouseDown=function(e){
  for(n=0;n<nomi.length;n++)
   if(e.target.id==nomi[n]){
    dragElement=e.target;
    offsetX=Nr(dragElement.style.left);
    offsetY=Nr(dragElement.style.top);
    startX=e.clientX;
    startY=e.clientY;
    for (n=0;n<document.getElementsByTagName('*').length;n++){
     document.getElementsByTagName('*')[n].style.zIndex=0;
    }
    dragElement.style.zIndex=1;
    document.onmousemove=onMouseMove;
   }
  return false;   
 }

 var onMouseMove=function(e){
  dragElement.style.left=offsetX+e.clientX-startX;
  dragElement.style.top=offsetY+e.clientY-startY;
 
 }

 var onMouseUp=function(e){
  document.onmousemove=null;
  for(n=0;n<nomi.length;n++)
   if(e.target.id==nomi[n]){
    var event=document.createEvent("Event");
    event.initEvent("Dropped",true,true);
    event.data=new Array(e.target,bersaglio(e));
    window.dispatchEvent(event);
   }
  dragElement=null;
 }
 var bersaglio=function(e){
  var oggetto;
  for(n=0;n<document.getElementsByTagName('*').length;n++){
   sinistra=Nr(document.getElementsByTagName('*')[n].style.left);
   alto=Nr(document.getElementsByTagName('*')[n].style.top);
   destra=sinistra+Nr(document.getElementsByTagName('*')[n].style.width);
   basso=alto+Nr(document.getElementsByTagName('*')[n].style.height);
   if(e.clientX>sinistra && e.clientX<destra && e.clientY>alto && e.clientY<basso)
    if(e.target != document.getElementsByTagName('*')[n])
     oggetto=document.getElementsByTagName('*')[n]
  }
 return oggetto;
 }
 inizializza();     

}
Piuttosto pesante, ma non credo ci sia possibilità di fare molto di meglio, in quanto anche l'autore di uno dei testi che ho trovato via web dice che non esiste altro modo che "scandagliare" il documento alla ricerca di aree occupate da altri oggetti, come fa qui la parte del codice scritta in blu.

Questo è il codice del modulo principale:
<script src="funzioni.js"></script>
<script>

Dragabile("due");
window.addEventListener("Dropped",function(event){event.data[0].innerHTML=event.data[1].id;},false); 
</script>
<body onload="inizia()">
<div id="uno" style="position:absolute;
  width:300px;
  height:100px;
  left:200px;
  top:100px;
  background:#CCFFCC;
  font-size:30px;
  "> 
</div>

<div id="due" style="position:absolute;
  width:300px;
  height:100px;
  left:500px;
  top:100px;
  background:#FFCCCC;
  font-size:30px;
  "> 
</div>
<img id="tre" style="width:300px;height:300px;position:absolute;left:0px;top:10px;" src="pallone.gif" />
</body>
Ecco, la parte scritta in rosso è quella che riceve l'evento una volta che l'elemento trascinabile è stato "droppato", e lo gestisce come meglio le pare a seconda della volontà del programmatore.

Credo che per essere un neofita non avrei potuto fare di meglio...

DragDrop con innesco di un evento e uso del relativo listener.

Ueeee!!! Fantastico!

La mia funzione DragDrop(), avente per parametri un numero variabile di nomi di elementi HTML, provvede egregiamente a rendere trascinabili gli elementi.
Adesso sono riuscito a far sì che l'evento di "droppaggio" dell'elemento inneschi un evento, per cui, ponendone un listener, si può intercettare con eventuali parametri (che ancora non so come inserire nell'evento).

Ecco il codice, un po' rudimentale in certi tratti, comunque l'idea è buona...
function DragDrop(){
 var nomi=new Array();
 var offsetX;
 var offsetY;
 var startX;
 var startY;
 var dragElement;
 for(n=0;n<arguments.length;n++)
  nomi[n]=arguments[n];
 
 var inizializza=function(){
  document.onmousedown=onMouseDown;
  document.onmouseup=onMouseUp; 
  document.onmouseover=onMouseOver;
 }
 var onMouseOver=function(e){
  for(n=0;n<nomi.length;n++)
   if(e.target.id==nomi[n]) e.target.style.cursor="pointer";
 }
 var onMouseDown=function(e){
  for(n=0;n<nomi.length;n++)
   if(e.target.id==nomi[n]){
    dragElement=e.target;
    offsetX=Nr(dragElement.style.left);
    offsetY=Nr(dragElement.style.top);
    startX=e.clientX;
    startY=e.clientY;
    for (n=0;n<document.getElementsByTagName('*').length;n++){
     document.getElementsByTagName('*')[n].style.zIndex=0;
    }
    dragElement.style.zIndex=1;
    document.onmousemove=onMouseMove;
   }
  return false;   
 }

 var onMouseMove=function(e){
  dragElement.style.left=offsetX+e.clientX-startX;
  dragElement.style.top=offsetY+e.clientY-startY;
 
 }

 var onMouseUp=function(e){
  document.onmousemove=null;
  for(n=0;n<nomi.length;n++)
   if(e.target.id==nomi[n]){
    var event=document.createEvent("Event");
    event.initEvent("Dropped",true,true);
    window.dispatchEvent(event);
   }
  dragElement=null;
 }
 inizializza();     

}
Quello in rosso è il codice che crea e triggera l'evento personalizzato "Dropped".
Questo è il codice presente nella mia libreria funzioni.js.
Nel modulo principale, invece, ho aggiunto il listener per questo evento:
<script src="funzioni.js"></script>
<script>

DragDrop("uno","due","tre");
window.addEventListener("Dropped",function(){alert("droppato!!!");},false);
</script>
<body onload="inizia()">
<div id="uno" style="position:absolute;
  width:300px;
  height:100px;
  left:200px;
  top:100px;
  background:green;
  "> 
</div>

<div id="due" style="position:absolute;
  width:300px;
  height:100px;
  left:500px;
  top:100px;
  background:red;
  "> 
</div>
<img id="tre" style="width:300px;position:absolute" src="pallone.gif">
</body>
...e quello in rosso è l'aggiunta dell'ascoltatore o listener.
In sostanza, quando un elemento viene droppato, l'evento si scatena comunque. Sta poi all'eventuale presenza di listeners la facoltà di volerlo intercettare o meno ponendo le necessarie conseguenze.

Lettura del foglio di stile esterno da javaScript

Funziona anche con il foglio di stile esterno, anche se lo scorrimento mi sembra un po' meno fluido...
<html>
<head>
<link rel="stylesheet" type="text/css" href="miostile.css">
<script type="text/javascript">

function Nr(stile){
 var numero=Number(stile.replace("px",""));
 return numero;
}

var oggetto, offsetX, offsetY, startX, startY;
function inizia(){
 document.onmousedown=MouseDown;
 document.onmouseup=MouseUp;
}

function MouseDown(e){
 oggetto=e.target;
 var stile=document.defaultView.getComputedStyle(oggetto,"");
 offsetX=Nr(stile.left);
 offsetY=Nr(stile.top);
 startX=e.clientX;
 startY=e.clientY;
 document.onmousemove=MouseMove;
 return false;
 
}

function MouseMove(e){
 oggetto.style.left=offsetX+e.clientX-startX;
 oggetto.style.top=offsetY+e.clientY-startY;

}

function MouseUp(){
 document.onmousemove=null;
 oggetto=null;
}
  
</script>
</head>

<body onLoad="inizia()">
<div id="uno" class="uno">
</div>

</body>
</html>

Lettura da parte del javaScript della posizione di un elemento impostata tramite un foglio di stile interno

Ecco la soluzione al fatto che il JavaScript non legge i fogli di stile interni:
<html>
<head>
<style>
.uno{
 position:relative;
 left:100px;
 top:50px;
 width:200px;
 height:100px;
 background:#AAFFCC;
 border:10px solid green;
}
</style>
<script type="text/javascript">

function Nr(stile){
 var numero=Number(stile.replace("px",""));
 return numero;
}

var oggetto, offsetX, offsetY, startX, startY;
function inizia(){
 document.onmousedown=MouseDown;
 document.onmouseup=MouseUp;
}

function MouseDown(e){
 oggetto=e.target;
 var stile=document.defaultView.getComputedStyle(oggetto,"");
 offsetX=Nr(stile.left);
 offsetY=Nr(stile.top);
 startX=e.clientX;
 startY=e.clientY;
 document.onmousemove=MouseMove;
 return false;
 
}

function MouseMove(e){
 oggetto.style.left=offsetX+e.clientX-startX;
 oggetto.style.top=offsetY+e.clientY-startY;

}

function MouseUp(){
 document.onmousemove=null;
 oggetto=null;
}
  
</script>
</head>

<body onLoad="inizia()">
<div id="uno" class="uno">
</div>

</body>
</html>
L'aggiunta della riga in rosso e dei valori su fondo giallo risolve il problema.
Come e perchè, ancora non lo capisco con precisione...

JavaScript e fogli di stile

Torno a rielaborare il mio (con l'aiuto di questo ottimo articolo) programmino per "dragare" oggetti.

Ormai credo di padroneggiare abbastanza la tecnica, dato che l'ho riscritto da solo su un altro computer in cui non avevo le mie librerie.

Però mi sono scontrato con un problema relativo ai fogli di stile, perchè le coordinate di un oggetto presenti su un foglio di stile non vengono lette da JavaScript...


Provo a riscrivere di sana pianta il codice per rendere "dragabile" un DIV.
<html>
<head>

<script type="text/javascript">

function Nr(stile){
 var numero=Number(stile.replace("px",""));
 return numero;
}

var oggetto, offsetX, offsetY, startX, startY;
function inizia(){
 document.onmousedown=MouseDown;
 document.onmouseup=MouseUp;
}

function MouseDown(e){
 oggetto=e.target;
 offsetX=Nr(oggetto.style.left);
 offsetY=Nr(oggetto.style.top);
 startX=e.clientX;
 startY=e.clientY;
 document.onmousemove=MouseMove;
 return false;
}

function MouseMove(e){
 oggetto.style.left=offsetX+e.clientX-startX;
 oggetto.style.top=offsetY+e.clientY-startY;

}

function MouseUp(){
 document.onmousemove=null;
 oggetto=null;
}
  
</script>
</head>

<body onLoad="inizia()">
<div id="uno" style= "
   position:relative;
   left:100px;
   top:50px;
   width:200px;
   height:100px;
   background:#AAFFCC;
   border:10px solid green;
   ">
</div>

</body>
</html>
...senza troppe difficoltà, salvo un paio di errori di distrazione per aver scritto un "offsetX" al posto di un "offsetY", e per aver trascurato il parametro e in MouseMove...
Bene: questo codice funziona egregiamente nel portare a spasso il DIV per tutta la finestra del browser.
Ora lo scrivo con un foglio di stile interno...
<html>
<head>
<style>
.uno{
 position:relative;
 left:100px;
 top:50px;
 width:200px;
 height:100px;
 background:#AAFFCC;
 border:10px solid green;
}
</style>
<script type="text/javascript">

function Nr(stile){
 var numero=Number(stile.replace("px",""));
 return numero;
}

var oggetto, offsetX, offsetY, startX, startY;
function inizia(){
 document.onmousedown=MouseDown;
 document.onmouseup=MouseUp;
}

function MouseDown(e){
 oggetto=e.target;
 offsetX=Nr(oggetto.style.left);
 offsetY=Nr(oggetto.style.top);
 startX=e.clientX;
 startY=e.clientY;
 document.onmousemove=MouseMove;
 return false;
}

function MouseMove(e){
 oggetto.style.left=offsetX+e.clientX-startX;
 oggetto.style.top=offsetY+e.clientY-startY;

}

function MouseUp(){
 document.onmousemove=null;
 oggetto=null;
}
  
</script>
</head>

<body onLoad="inizia()">
<div id="uno" class="uno">
</div>

</body>
</html>
E vediamo cosa succede...

Ecco: quando faccio il MouseDown tutto bene... Quando poi inizio il MouseMove il mio DIV mi scatta all'indietro e in alto, andandosi a rifugiare nell'angolo superiore sinistro della finestra.
Perchè?

Con una tecnica molto rudimentale ma efficace, pongo un comando "spia" nel mio codice:
function MouseDown(e){
 oggetto=e.target;
 offsetX=Nr(oggetto.style.left);
 offsetY=Nr(oggetto.style.top);
 startX=e.clientX;
 startY=e.clientY;
 document.onmousemove=MouseMove;
 alert(offsetX+" "+offsetY);
 return false;
}
Questo mi dà il valore delle due variabili in cui va immagazzinata la posizione iniziale del DIV quando si fa il MouseDown su di esso: esso mi risulta, da questa prova pari a 0 e 0. Quindi, non appena faccio il MouseMove, la nuova posizione del DIV mi viene calcolata non a partire dalla posizione iniziale del DIV, ma da zero!
Se ad esempio la posizione X iniziale del DIV è 100, e il mouse si muove di 10, la posizione calcolata nell'ambito del MouseMove dovrebbe essere 100+10=110 perchè in offsetX è stata memorizzata la posizione iniziale del DIV che era 100; se invece in offsetX la posizione iniziale memorizzata è zero, all'atto del MouseMove il calcolo non viene fatto con 100 ma con 0, e quindi il DIV si sposta a una posizione sull'asse delle X di 0+10=10, e non 110!
Sono arrivato alla conclusione che se la posizione del DIV è specificata in un foglio di stile, il codice javaScript non la legge, ponendo quindi nuovi problemi...
A maggior ragione ciò accade con fogli di stile esterni...

domenica 29 gennaio 2012

Studio di createEvent, initMouseEvent, dispatchEvent

Analizziamo questo codice per cercare di ricordarlo meglio...

function evoca(){
 var oggetto=document.getElementById("uno");
 var evento=document.createEvent("MouseEvents")
 evento.initMouseEvent('click', true, true, window, 1, 12, 345, 7, 220, false, false, true, false, 0, null)
 oggetto.dispatchEvent(evento);
}
Si tratta di tre istruzioni:
  • createEvent
  • initMouseEvent
  • dispatchEvent

createEvent è riferito all'oggetto document.
initMouseEvent è riferito all'evento creato.
dispatchEvent è riferito all'oggetto al quale si attribuisce l'evento.
Ora vediamo il parametro di createEvent.
Il parametro di createEvent che ho visto è MouseEvents, che sta per eventi del mouse, dei quali quelli che mi vengono in mente sono click, mousedown, mouseup, mouseover, mouseove, mouseout.

Per quanto riguarda initMouseEvent, che inizializza gli eventi del tipo MouseEvents: Se la scrivo così forse mi viene più facile memorizzare...
evento.initMouseEvent("click",true,true,window,
    1,0,0,0,0,
    false,false,false,false,
    0,null)
Ecco: il primo gruppo riguarda il nome dell'evento, il più facile, e, credo, lo scope dell'evento. In mezzo ci sono valori riguardanti il bubbling e la cancellazione dell'evento, che devo ancora imparare ma che credo si possano lasciare a true.

Il secondo gruppo è fatto da un'informazione chiamata detali e dalle coordinate screenX, screenY, clientX, clientY.

Il terzo gruppo riguarda i tasti da premere insieme al mouse: ctrlKey, altKey, shiftKey, metaKey, e credo possano restare tranquillamente a false.

L'ultimo gruppo è dato dal bottone premuto sul mouse e dal relatedTarget.

Bene... forse è il caso di vedere nei dettagli ogni singola voce. ma per il momento credo sia più pratico esercitarsi a scrivere createEvent e initMouseEvent con una certa scioltezza...

Creare eventi e chiamarli dall'esterno...

La prima parte del codice di cui sopra non serve!
Il fatto che uso addListener aggiunge semplicemente un "ascoltatore" per un evento a un elemento del documento, e basta.

E' come il classico onclick che aggiungo nei tags di ogni elemento della pagina. I vantaggi che sono riuscito a vedere finora sono che la risposta di un elemento a un evento può essere variata a seconda delle esigenze, mettendo o rimuovendo il listener, e che si può aggiungere e togliere il listener a una serie di elementi (dovrebbe esistere anche l'istruzione per rimuoverlo, suppongo!)

Ora invece sono riuscito a far funzionare un codice che crea un evento.
E' piuttosto complicato.

<html>
<head>

<script type="text/javascript">
function evoca(){
 var oggetto=document.getElementById("uno");
 var evento=document.createEvent("MouseEvents")
 evento.initMouseEvent('click', true, true, window, 1, 12, 345, 7, 220, false, false, true, false, 0, null)
 oggetto.dispatchEvent(evento);
}
</script>
</head>

<body>
<div id="uno" style="background:green;width:100px;height:100px" onClick="alert('cliccato')"></div>
<div id="due" style="background:cyan;width:200px;height:100px;position:absolute;left:200px;top:10px" onMouseOver="evoca();"></div>
</body>
</html>
Ecco, in questo codice il DIV "uno" ha già un evento onClick. Il codice che ho marcato in rosso serve a simulare il click su quell'elemento. Tutto qua.
Se poi il codice viene chiamato da un altro evento in un altro elemento, allora posso simulare l'evento stesso, come in questo caso dove simulo un click sul DIV "uno" passando il mouse sul DIV "due".