Indice del forum Olimpo Informatico
I Forum di Zeus News
Leggi la newsletter gratuita - Attiva il Menu compatto
 
 FAQFAQ   CercaCerca   Lista utentiLista utenti   GruppiGruppi   RegistratiRegistrati 
 ProfiloProfilo   Messaggi privatiMessaggi privati   Log inLog in 

    Newsletter RSS Facebook Twitter Contatti Ricerca
JavaScript e timing
Nuovo argomento   Rispondi    Indice del forum -> Linguaggi per Internet
Precedente :: Successivo  
Autore Messaggio
freemind
Supervisor sezione Programmazione
Supervisor sezione Programmazione


Registrato: 04/04/07 21:28
Messaggi: 4643
Residenza: Internet

MessaggioInviato: 12 Apr 2008 13:59    Oggetto: JavaScript e timing Rispondi citando

Salve forum.
Di solito scrivo poco e normalmente i miei post sono in risposta a qualcuno.
Oggi scrivo qui perchè in questi giorni mi sono imbattuto su un simpatico problema: le funzioni di timing di javascript applicate ai metodi di una classe da me creata. La soluzione credo possa interessare molti.
Tutti sanno come si gestiscono i timer in js: ad esempio setTimeout("funz()",millisec) fa sì che la funzione funz() venga chiamata dopo millisec millisecondi e nel frattempo l'esecuzione del codice prosegue alla riga successiva. Orbene (che parolone), se proviamo a chiamare setTimeout(...) dentro ad una classe per farle eseguire un metodo, tutto va a ramengo!
Lo stesso discorso vale per setInterval(...) e tutte le funzioni di timing.
Ho trovato per la rete (w internet!) il modo di risolvere il problema.
Ora mostrerò come scrivere una semplice classe Countdown che implementaerà un countdown in secondi.
Il codice (dovrebbe) funzionare sia con ie che con gli altri browser (per ie vedremo che 2 palle dovremo farci).
Prima di tutto dobbiamo porci la domanda: "Perchè non funziona un tubo se uso setTimeout(...) al solito modo?".
La risposta è: "Il problema è che non riusciamo a chiamare window.setTimeout nel contesto della nostra classe per cui this è fuori scope".
Lo script non funziona (neppure ci provo ad eseguirlo, tanto non va):
Codice:

// classe Countdown
this.Countdown=function(sec,targetDiv)
{
// div che conterrà il timer
   this.tdiv = document.getElementById(targetDiv);
// secondi di durata del conteggio
   this.sec = sec
   
// metodo pubblico per eseguire il countdown
   this.displayCountdown=makeCountdown;
}

// questo metodo tenta di implementare il timer   
function makeCountdown()
{
   if (this.sec==0)
      this.tdiv.innerHTML="fine";
   else
      this.tdiv.innerHTML=this.sec;
   --this.sec;
   
   if (this.sec>-1)   
      setTimeout("displayCountdown()",1000);   // (*), non va!
   
}

Se da qualche parte provate ad usare 'sto coso avrete degli errori sulla chiamata (*)

Dobbiamo fare in modo di eseguire la chimata nel contesto del nostro oggetto.
Con firefox possiamo utilizzare la versione che segue:
Codice:

// classe Countdown
this.Countdown=function(sec,targetDiv)
{
// div che conterrà il timer
   this.tdiv = document.getElementById(targetDiv);
// secondi di durata del conteggio
   this.sec = sec
   
// metodo pubblico per eseguire il countdown
   this.displayCountdown=makeCountdown;
}


function makeCountdown()
{
   
   if (this.sec==0)
      this.tdiv.innerHTML="fine";
   else
      this.tdiv.innerHTML=this.sec;
   --this.sec;
   
   if (this.sec>-1)
      setTimeout(function(thisObj) {thisObj.displayCountdown(); },1000,this); // (**)
   
}

Bene bene, resta il problema ie.
Purtroppo le funzioni di timing di ie non supportano l'ultimo parametro passato a (**) per cui non possiamo referenziare la nostra istanza.
Dobbiamo creare un vettore globale che conterrà i riferimenti alle nostre istanze e utilizzare una funzione esterna per scegliere quale istanza andare a prendere per la chiamata.
Il codice che segue implementa la classe in modo che funzioni su ogni browser (almeno i più noti: ff,safari,opera,ie,konqueror)
Codice:

<script type="text/javascript">
   // var globali per gestire le chiamate per ie
   var globalScope_Counter = new Array();   // vettore delle istanze
   var globalIndex_Counter = 0;   // indice del precedente
   
   this.Countdown=function(sec,targetDiv)
   {
   /*
      Dobbiamo creare un id univoco dell'istanza
      e caricare questa nel vettore
   */
      this.uniqueId = 'Obj' + globalIndex_Counter++;
      globalScope_Counter[this.uniqueId]=this;
   
   // div che conterrà il timer
      this.tdiv = document.getElementById(targetDiv);
   // secondi di durata del conteggio
      this.sec = sec
      
   // metodo pubblico per eseguire il countdown
      this.displayCountdown=makeCountdown;
   }

   function makeCountdown()
   {
      
      if (this.sec==0)
         this.tdiv.innerHTML="fine";
      else
         this.tdiv.innerHTML=this.sec;
      --this.sec;
      
      if (this.sec>-1)
      {
         // ie
         if (document.all) // (***)
         {
            setTimeout( 'ieIntervalHandler("' + this.uniqueId + '","displayCountdown")', 1000 );
         }
         else
         {
            setTimeout(function(thisObj) {thisObj.displayCountdown(); },1000,this); // (**)
         }
      }
      
   }
// gestore delle chiamate per ie   
   function ieIntervalHandler( id, strFunc )
   {
      /* Scope corretto per la chiamata */
      var scope = globalScope_Counter[id];
      eval( "scope." + strFunc + "()" );
   }
</script>

La condizione (***) verifica se stiamo usando ie o altro.
Nel primo caso applica setTimeout(...) alla funzione ieIntervalHandler(...) che vuole come parametri l'id dell'istanza e il metodo di questa che vogliamo chiamare dopo 1000 (in questo caso) millisecondi.
Il tutto si gioca tramite la funzione eval(...) che data la stringa passata genera del codice javascript.
Qui c'è un esempio di utilizzo di eval (così chi ha bisogno può chiarirsi le idee).
link

Per usare lo script possiamo scrivere qualcosa del genere (non mi soffermo sulla bellezza del codice html così come non ho pignolato sul controllo degli errori delle funzioni js)
Codice:

<html>
<head>

<script type="text/javascript">
// copiate qui l'ultma versione dello script
</script>

</head>
<body>
   <div id="timer1"></div>
   <div id="timer2"></div>
   <script type="text/javascript">
      
      var c1=new Countdown(5,"timer1");
      c1.displayCountdown();
      var c2=new Countdown(8,"timer2");
      c2.displayCountdown();
   </script>
</body>
</html>

L'ultima versione può causare dei memoryleak dovuti al sovraccarico del vettore globale nel caso utilizzassimo più contatori nella pagina. Infatti dopo la chiamata
Codice:
var c=new Countdown(5,"timer");

le prime righe del costruttore salvano a priori l'oggetto nel vettore indipendentemente che si tratti di ie o ff. Si potrebbe tentare di salvare nel vettore solo se il browser è ie modificando il costruttore nel modo che segue:
Codice:

   this.Countdown=function(sec,targetDiv)
   {
   /*
      Dobbiamo creare un id univoco dell'istanza
      e caricare questa nel vettore
   */
      if (document.all)
      {
         this.uniqueId = 'Obj' + globalIndex_Counter++;
         globalScope_Counter[this.uniqueId]=this;
      }
   
   // div che conterrà il timer
      this.tdiv = document.getElementById(targetDiv);
   // secondi di durata del conteggio
      this.sec = sec
      
   // metodo pubblico per eseguire il countdown
      this.displayCountdown=makeCountdown;
   }
   ...
   ...

La variante funziona anche se non ho fatto test per verificare il problema dei memoryleak ma a fiuto vettore più piccolo implica meno memoria occupata e quindi meno probabilità di memoryleak dovuti a deallocazioni fallite.

Spero che tutta questa cosa (lunghissima) possa aiutare chi,come me, si è trovato davanti a questo problema che all'inizio mi sembrava non avere senso.

Salve a tutti
Top
Profilo Invia messaggio privato
horus
Macchinista
Macchinista


Registrato: 22/03/05 10:48
Messaggi: 2554
Residenza: Sirio e dintorni

MessaggioInviato: 14 Apr 2008 08:37    Oggetto: Rispondi

Sinceramente non mi sono mai trovato a dover affrontare il problema ma la tua trattazione è decisamente esauriente e se ne avrò bisogno mi ricorderò di questo post. Applause Applause
Top
Profilo Invia messaggio privato
Mostra prima i messaggi di:   
Nuovo argomento   Rispondi    Indice del forum -> Linguaggi per Internet Tutti i fusi orari sono GMT + 2 ore
Pagina 1 di 1

 
Vai a:  
Non puoi inserire nuovi argomenti
Non puoi rispondere a nessun argomento
Non puoi modificare i tuoi messaggi
Non puoi cancellare i tuoi messaggi
Non puoi votare nei sondaggi