ugrás a tartalomhoz

érdekes JS probléma: a script először rosszul működik (MINDEN ESETBEN!), majd megjavul...

fchris82 · 2008. Aug. 13. (Sze), 14.32
A problémát röviden a kód alatt leírom. FF3 alatt néztem. A komplett kód, ki is lehet próbálni (protoype.js):
  1. <html>  
  2. <head>  
  3. <script language="javascript" src="prototype.js"></script>  
  4. <script type="text/javascript">  
  5. /**  
  6.  * Timer  
  7.  * Időzítő. Amint betöltődött az oldal fél másodpercenként lefut.  
  8.  */  
  9. var Timer = {  
  10.   // Ebben a tömbben tárolja, hogy mely objektumok runTimer() fv-t kell meghívni minden fél másodpercben.  
  11.   container:  [],  
  12.   // Ebben a változóban követjük, hogy befejeződött-e az előző hivás, és ha nem, akkor nem hívható kétszer egymás után!!!  
  13.   isRun:      false,  
  14.   // Itt nézzük, hogy el lett-e már indítva az időzítő  
  15.   isStarted:  false,  
  16.   
  17.   // Itt adunk hozzá új visszahivandó objektumot.  
  18.   add: function(element, overwrite) {  
  19.     added = this.hasAdded(element);  
  20.     if(!overwrite && added) {  
  21.       return false;  
  22.     } else if(added) {  
  23.       this.remove(element);  
  24.     }  
  25.   
  26.     this.container[this.container.length] = element;  
  27.     return true;  
  28.   },  
  29.   
  30.   // Itt távolítjuk el a megadott objektumot, ha arra már nincs szükség.  
  31.   remove: function(element) {  
  32.     thisthis.container = this.container.without(element);  
  33.   },  
  34.   
  35.   // Megnézi, hogy a megadott elem hozzá lett-e már adva.  
  36.   hasAdded: function(element) {  
  37.     return (this.container.indexOf(element)>=0);  
  38.   },  
  39.   
  40.   // Elindítja az időzítőt  
  41.   start: function(event) {  
  42.     // Csak akkor lehet elindítani az időzítőt, ha az még nincs elindítva.  
  43.     if(!this.isStarted)  
  44.     {  
  45.       window.setInterval('Timer.run()', 500);  
  46.       this.isStarted = true;  
  47.     }  
  48.   },  
  49.   
  50.   // Ezt hívja meg fél másodpercenként  
  51.   run: function() {  
  52.     if(!this.isRun) {  
  53.       // Elkezdődik a tömb bejárása  
  54.       this.isRun = true;  
  55.       // Az objektumoknak átadja a az aktuális időpontot  
  56.       d = new Date();  
  57.       // Bejárja a container tömböt és egyesével meghívja az ott lévő objektumok callTimer() fv-ét  
  58.       this.container.each(function(element) {  
  59.         element.callTimer(d);  
  60.       });  
  61.       // Befejeződött a tömb bejárása  
  62.       this.isRun = false;  
  63.     }  
  64.   }  
  65. }  
  66.   
  67. // Várakoztató "osztály". Több ilyen is lehet. Akkor kerül egy "várakoztatóba" két objektum,  
  68. // ha a második csak akkor kezdődhet el számolni, ha az előző már lefutott  
  69. var Delayer = Class.create({  
  70.   // Mikor kezdett el várakozni a this.current elem.  
  71.   delayStart: null,  
  72.   // Az aktuálisan várakozó objektum  
  73.   current: null,  
  74.   // Az objektum lista, ami várakozik  
  75.   container: [],  
  76.   // Éppen várakozik-e vmi  
  77.   isRun: false,  
  78.   name: null,  
  79.   
  80.   initialize: function(name) {  
  81.     this.name = name;  
  82.   },  
  83.   
  84.   start: function(element) {  
  85.     this.container[this.container.length] = element;  
  86.     Timer.add(this, false);  
  87.   },  
  88.   
  89.   // Eltávolít egy várakozó objektumot  
  90.   cancel: function(element) {  
  91.     thisthis.container = this.container.without(element);  
  92.   },  
  93.   
  94.   // Alaphelyzetbe állítja a "várakoztatót"  
  95.   reset: function(element) {  
  96.     // Törli az elemet a listáról  
  97.     if(typeof(element)!='undefined') {  
  98.       this.cancel(element);  
  99.     }  
  100.     this.current = null;  
  101.     this.delayStart = null;  
  102.     if(document.test) {  
  103.       document.test.logta.value+="----- Reset: "+this.name+"\n";  
  104.     }  
  105.   },  
  106.   
  107.   // Eggyel továbblépteti a várakozó elemek listáján.  
  108.   next: function(d) {  
  109.     // Ha nincs több elem, akkor kilép  
  110.     if(this.container.length==0) {  
  111.       this.reset();  
  112.       return false;  
  113.     }  
  114.   
  115.     thisthis.current = this.container.first();  
  116.     this.delayStart = d;  
  117.     return true;  
  118.   },  
  119.   
  120.   callTimer: function(d) {  
  121.     // Ha az aktuális elem null és nincs tovább  
  122.     if(this.current==null && !this.next(d)) {  
  123.       Timer.remove(this);  
  124.       return false;  
  125.     }  
  126.     document.test.logta.value+=d.getTime()+": "+this.name+" ("+this.delayStart.getTime()+") ["+this.current.n+"]\n";  
  127.   
  128.     // Ennyi másodpercet kell várnia az adott objektumnak  
  129.     w        = this.current.wait;  
  130.     // Ennyi másodperc van még hátra  
  131.     distance = w-parseInt((d.getTime()-this.delayStart.getTime())/1000)-1;  
  132.     if(distance>0) {  
  133.       this.current.status('Hátra van:'+distance+' s');  
  134.     } else {  
  135.       this.current.run(d);  
  136.       this.reset(this.current);  
  137.     }  
  138.   },  
  139. });  
  140.   
  141. var Obj = Class.create({  
  142.   // Az objektum sorszáma  
  143.   n: 0,  
  144.   // Ennyi másodpercet várjon  
  145.   wait: 5,  
  146.   
  147.   // ahova írkálnia kell  
  148.   html: null,  
  149.   
  150.   initialize: function(n) {  
  151.     this.n = n;  
  152.     this.html = new Element('div', {'id': 'obj_'+n}).update('init');  
  153.     document.body.appendChild(this.html);  
  154.   },  
  155.   
  156.   run: function(d) {  
  157.     this.status('Kész: '+this.n);  
  158.   },  
  159.   
  160.   status: function(msg) {  
  161.     this.html.update(msg);  
  162.   }  
  163. });  
  164.   
  165. var counter = 0;  
  166. var d1 = new Delayer('d1');  
  167. function startDelay1() {  
  168.   document.test.logta.value+="+++++ Klikk: Delayer1\n";  
  169.   d1.start(new Obj(counter));  
  170.   counter++;  
  171. }  
  172. var d2 = new Delayer('d2');  
  173. function startDelay2() {  
  174.   document.test.logta.value+="+++++ Klikk: Delayer2\n";  
  175.   d2.start(new Obj(counter));  
  176.   counter++;  
  177. }  
  178. </script>  
  179. </head>  
  180.   
  181. <body>  
  182. <script>  
  183. document.body.onload = Timer.start();  
  184. </script>  
  185. <form name="test">  
  186. <textarea name="logta" cols="100" rows="40" style="float: right"></textarea>  
  187. </form>  
  188. <div id="message" onclick="javascript: startDelay1()">Delayer1</div>  
  189. <div id="message2" onclick="javascript: startDelay2()">Delayer2</div>  
  190. </body>  
  191. </html>  
A hiba reprodukálása:
1. Kattints egymás után kétszer a Delay1 szövegre, majd gyorsan a Delay2 szövegre. Ekkor 4 új sor lesz. Normális működés esetén az 1. és a 3. sornak számolnia kellene. És 10 másodperc alatt mindenhol azt kéne kiírnia, hogy kész, de ezzel ellentétben 20 másodperc alatt megy végig és egyesével.
2. Várd végig, míg mind a négy sornál azt írja ki, hogy kész. Most ismételd meg az előző lépést. Kettőt kattints a Delay1-en és kettőt a Delay2-n. Láss csodát, normálisan működik.

A probléma röviden: Van egy időzítő (Timer), ami az oldal betöltése után elindul és fél másodpercenként meghívogatja a neki átadott objektumokat (egészen pontosan az objektumok runTimer() fv-ét). Itt a példában két objektumot kap kattintásra, amik azt csinálják, hogy várakoznak 5 másodpercet, annyiszor, ahányszor rákattintasz. Amikor az elsőre kattintok, akkor elindul a számláló. Ha ugyanekkor a másodikra, akkor annak is számolnia kellene! De nem ezt csinálja :( A this.current-re az első objektum current-jét használja! De csak az elején. Ha végigvárom a kattintások eredményeit, majd újra kattintok, akkor már minden jól működik. Miért??? A logban látszik, hogy egy darabig a másodikként elindított várakoztató az elsőnek az objektumával "dolgozik". Ugyanakkor néha a saját delayStart változóját használja, néha az elsőnek létrehozott objektumét... IE alatt nem is akar működni, a hibakeresője teljesen használhatatlan és értelmetlen üzeneteket ad. Nem a hiba valódi okát adja meg. Most a hiba az én gépemben van (telepítsem újra? :D ), vagy a scriptben?

Ha a hibát reprodukálod, a logban látható, hogy a Delay1 (d1) és a Delay2 (d2) ugyanazzal az elemmel foglalkozik (szögletes zárójelbe írt szám), tehát a this.current mindkettőjük esetében ugyanaz! DE! A this.delayStart (sima zárójelbe írt szám) meg mégsem ugyanaz, azt jól kezeli, tehát nem keveri a két objektum adatát.

Tehát: hol a hiba, hogy ez így működik? (tegnap este még működött rendesen, ma mikor bekapcsoltam újra a gépet, azután kezdte el ezt)
IE alatt tegnap sem működött, tudna vki segíteni, hogy miért nem? Nem vagyok vmi nagy JS guru :( (még :) )

Az "Objektumorientált JavaScript programozás a felszín fölött" cikket olvastam, úgyhogy ha ott van válasz, csak én siklottam el felette, akkor vissza lehet rá utalni, és akkor előre is elnézést kérek :)
 
1

Most komolyan?

vbence · 2008. Aug. 13. (Sze), 15.11
Ennél jobban nem tudnád leszűkíteni a dolgot? Például egyenként kidobálni a metódusokat...
4

Ez már az egyszerűsítés után van :)

fchris82 · 2008. Aug. 13. (Sze), 16.11
Hali!

Az eredeti kód ennél jóval hosszabb, ez már gyomlálás után van, de az észrevételed után tovább kopasztottam, most már tényleg csak a lényegi rész van benne.
IE probléma megoldva. A bemásolt kód 138. sorában van egy vessző, ami nem tetszett neki. Persze a világért sem ezt írta ki... Szinte soronként elkezdtem átmásolni egy másik fájlba a forrást, úgy találtam meg a hibát.

Átírtam, így már működik IE alatt is, és ott is ezt a tünetet produkálja. Itt az új kód:
  1. <html>  
  2. <head>  
  3. <script language="javascript" src="prototype.js"></script>  
  4. <script type="text/javascript">  
  5. var Timer = {  
  6.   container:  [],  
  7.   
  8.   add: function(element) {  
  9.     this.container[this.container.length] = element;  
  10.   },  
  11.   
  12.   remove: function(element) {  
  13.     thisthis.container = this.container.without(element);  
  14.   },  
  15.   
  16.   start: function(event) {  
  17.     window.setInterval('Timer.run()', 500);  
  18.   },  
  19.   
  20.   run: function() {  
  21.     d = new Date();  
  22.     this.container.each(function(element) {  
  23.       element.callTimer(d);  
  24.     });  
  25.   }  
  26. }  
  27.   
  28. var Delayer = Class.create({  
  29.   delayStart: null,  
  30.   current: null,  
  31.   container: [],  
  32.   // Éppen várakozik-e vmi  
  33.   isRun: false,  
  34.   name: null,  
  35.   
  36.   initialize: function(name) {  
  37.     this.name = name;  
  38.   },  
  39.   
  40.   start: function(element) {  
  41.     this.container[this.container.length] = element;  
  42.     Timer.add(this);  
  43.   },  
  44.   
  45.   cancel: function(element) {  
  46.     thisthis.container = this.container.without(element);  
  47.   },  
  48.   
  49.   reset: function(element) {  
  50.     // Törli az elemet a listáról  
  51.     if(typeof(element)!='undefined') {  
  52.       this.cancel(element);  
  53.     }  
  54.     this.current = null;  
  55.     this.delayStart = null;  
  56.     if(document.test) {  
  57.       document.test.logta.value+="----- Reset: "+this.name+"\n";  
  58.     }  
  59.   },  
  60.   
  61.   next: function(d) {  
  62.     // Ha nincs több elem, akkor kilép  
  63.     if(this.container.length==0) {  
  64.       this.reset();  
  65.       return false;  
  66.     }  
  67.   
  68.     thisthis.current = this.container.first();  
  69.     this.delayStart = d;  
  70.     return true;  
  71.   },  
  72.   
  73.   callTimer: function(d) {  
  74.     // Ha az aktuális elem null és nincs tovább  
  75.     if(this.current==null && !this.next(d)) {  
  76.       Timer.remove(this);  
  77.       return false;  
  78.     }  
  79.     document.test.logta.value+=d.getTime()+": "+this.name+" ("+this.delayStart.getTime()+") ["+this.current.n+"]\n";  
  80.   
  81.     w        = this.current.wait;  
  82.     distance = w-parseInt((d.getTime()-this.delayStart.getTime())/1000)-1;  
  83.     if(distance>0) {  
  84.       this.current.status('Még vár:'+distance+' s');  
  85.     } else {  
  86.       this.current.start(d);  
  87.       this.reset(this.current);  
  88.     }  
  89.   }  
  90. });  
  91.   
  92. var Obj = Class.create({  
  93.   // Objektum sorszáma  
  94.   n: 0,  
  95.   // Ennyi másodpercet várakozzon  
  96.   wait: 5,  
  97.   // Ebben írkáljuk, hogy mi van  
  98.   html: null,  
  99.   
  100.   initialize: function(n) {  
  101.     this.n = n;  
  102.     this.html = new Element('div', {'id': 'obj_'+n}).update('init');  
  103.     document.body.appendChild(this.html);  
  104.   },  
  105.   
  106.   start: function(d) {  
  107.     this.status('Kész: '+this.n+'. elem');  
  108.   },  
  109.   
  110.   status: function(msg) {  
  111.     this.html.update(msg);  
  112.   }  
  113. });  
  114.   
  115. Event.observe(window, 'load', Timer.start);  
  116.   
  117. var counter = 0;  
  118. var d1 = new Delayer('d1');  
  119. function startDelay1() {  
  120.   document.test.logta.value+="+++++ Klikk: d1\n";  
  121.   d1.start(new Obj(counter));  
  122.   counter++;  
  123. }  
  124. var d2 = new Delayer('d2');  
  125. function startDelay2() {  
  126.   document.test.logta.value+="+++++ Klikk: d2\n";  
  127.   d2.start(new Obj(counter));  
  128.   counter++;  
  129. }  
  130. </script>  
  131. </head>  
  132.   
  133. <body>  
  134. <form name="test">  
  135. <textarea name="logta" cols="100" rows="40" style="float: right"></textarea>  
  136. </form>  
  137.   
  138. <div id="message" onclick="javascript: startDelay1()">Delay 1.</div>  
  139. <div id="message2" onclick="javascript: startDelay2()">Delay 2.</div>  
  140.   
  141. </body>  
  142. </html>  
Egyszerűsíteném én jobban, de a probléma így globál jelentkezik...
A gond tehát az, hogy létrehozok két "várakoztatót". Legyenek ezek mondjuk jegyellenőrök. A jegyellenőrnek meg van mondva, hogy a biztonsági emberke 5 másodperc alatt ellenőriz le egy vendéget. A jegyellenőr feladata, hogy pontosan 5 másodperc múlva engedje tovább a vendéget, addig ne! A jegyellenőr és a biztonsági emberke egyszerre csak egy emberrel tud foglalkozni, így ha egy ellenőrhöz 2-3 ember áll be a sorba, akkor szépen mindegyiknek ki kell várnia a sorát. (a container tömbbe kerülnek az "emberkék")
Esetünkben viszont a gyorsabb haladás miatt két jegyellenőr van létrehozva, így az emberek két helyre is állhatnak. Amikor a Delay 1-re kattintasz, akkor egy emberkét beállítasz a sorba az 1. jegyellenőrhöz, a Delay 2-nél pedig a 2. jegyellenőrhöz.
Ha 4 emberkéd van, és 2-2 emberkét állítasz be a sorba, akkor 10 másodperc alatt mindnek be kéne jutnia. Ellenben a scriptnél a következő történik:
Beállítasz 2-2 embert, a két jegyellenőrhöz. Csakhogy nem külön-külön elkezdenek foglalkozni a sajátjaikkal, hanem mindkettő ugyanazzal az emberrel foglalkozik! A 'this.container' változó mintha mindkét objektumban "megegyezne", így mikor hozzáadok egy-egy emberkét mindkét sorhoz, abból nem két sor lesz, hanem 1! De csak amíg be nem jut mindegyik ember, és üres nem lesz mindkét sor. Mert ha ekkor megint hozzáadok 1-1 embert a jegyellenőrökhöz, akkor már 2 sor fog kialakulni és helyesen működik, tehát a 'this.container' már mindkét objektumban más!

Máshogy, nagyon lebutítva, csak a tünetre koncentrálva:
  1. a1 = new A();  
  2. b1 = new A();  
  3.   
  4. a1.x = 1;  
  5. b1.x = 2;  
  6.   
  7. alert(a1.x) //--> 1 helyett 2!!!  
  8. alert(b1.x) //--> 2  
  9.   
  10. // majd megint lefuttatjuk  
  11. a1.x = 3;  
  12. b1.x = 4;  
  13.   
  14. // És most vmiért működik o_O  
  15. alert(a1.x) //--> 3  
  16. alert(b1.x) //--> 4  
7

Ugyanaz

Poetro · 2008. Aug. 13. (Sze), 17.34
Azért folgalkoznak ugyanazzal, mert a Timer objektumod ugyanaz.
8

Nem

fchris82 · 2008. Aug. 13. (Sze), 17.38
Lásd a 6. hozzászólást. A probléma megoldódott, a prototype Class.create() fv-ben van a hiba.
2

Nem értem

Poetro · 2008. Aug. 13. (Sze), 15.21
Sajnos egyáltalán nem értem, mit akarsz csinálni, pedig próbáltam. Nálam egy
+++++ Klikk: Delayer[12]
illetve egy init felirat jelenik meg és ennyi.
3

prototype

rrd · 2008. Aug. 13. (Sze), 15.39
Én is belefutottam már pár apróbb ügybe a a prototype-pal FF3-mal. Próbáld ki FF2-vel is. Ha azzal jó, akkor várd meg míg kijön az új prototype FF3 támogatással :)
5

Erre nem is gondoltam...

fchris82 · 2008. Aug. 13. (Sze), 16.16
Mármint arra, hogy a prototype kódjában van a hiba és nem az enyémben :) Az előbbi hozzászólásomban írtam éppen, hogy IE alatt is jelentkezik ez a probléma, így mindjárt meg is nézem, hogy hátha a prototype-ben van a hiba.
6

prototype

fchris82 · 2008. Aug. 13. (Sze), 16.44
A prototype-ban volt a hiba. Az üres tömböt hibásan hozza létre. Hogy miként, azt még nem fejtettem meg, de ha a második kód 26. sortól átírom így (hozzáírva az initialize részhez ez: this.container = []; )
  1. var Delayer = Class.create({  
  2.   // ...  
  3.   // Ezt továbbra is így hagyom, mert "így helyes"  
  4.   container: [],  
  5.   // ...  
  6.   
  7.   initialize: function(name) {  
  8.     this.name = name;  
  9.     // Az alábbi sor került beszúrásra  
  10.     this.container = [];  
  11.   },  
akkor a probléma megoldódik és rendesen működik a script. Köszönöm mindenkinek, aki segíteni próbált :)
10

prototype

rrd · 2008. Aug. 14. (Cs), 09.29
ha lehet küldj nekik egy bug reportot, így biztos belekerül a ajvítás a következő verzióba.
9

onload

SamY · 2008. Aug. 14. (Cs), 07.41
szia, többek közt megjegyezném:
  1. document.body.onload = Timer.start();  
ez így nem onload -ra fog lefutni:) így igen:
  1. document.body.onload = Timer.start;  
11

Valóban

fchris82 · 2008. Aug. 14. (Cs), 14.14
Igen, ha megnézed a másodikra bemásolt kódot, ott már átírtam ezt is, mert rájöttem, hogy az is rossz.