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):

<html>
<head>
<script language="javascript" src="prototype.js"></script>
<script type="text/javascript">
/**
 * Timer
 * Időzítő. Amint betöltődött az oldal fél másodpercenként lefut.
 */
var Timer = {
  // Ebben a tömbben tárolja, hogy mely objektumok runTimer() fv-t kell meghívni minden fél másodpercben.
  container:  [],
  // 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!!!
  isRun:      false,
  // Itt nézzük, hogy el lett-e már indítva az időzítő
  isStarted:  false,

  // Itt adunk hozzá új visszahivandó objektumot.
  add: function(element, overwrite) {
    added = this.hasAdded(element);
    if(!overwrite && added) {
      return false;
    } else if(added) {
      this.remove(element);
    }

    this.container[this.container.length] = element;
    return true;
  },

  // Itt távolítjuk el a megadott objektumot, ha arra már nincs szükség.
  remove: function(element) {
    this.container = this.container.without(element);
  },

  // Megnézi, hogy a megadott elem hozzá lett-e már adva.
  hasAdded: function(element) {
    return (this.container.indexOf(element)>=0);
  },

  // Elindítja az időzítőt
  start: function(event) {
    // Csak akkor lehet elindítani az időzítőt, ha az még nincs elindítva.
    if(!this.isStarted)
    {
      window.setInterval('Timer.run()', 500);
      this.isStarted = true;
    }
  },

  // Ezt hívja meg fél másodpercenként
  run: function() {
    if(!this.isRun) {
      // Elkezdődik a tömb bejárása
      this.isRun = true;
      // Az objektumoknak átadja a az aktuális időpontot
      d = new Date();
      // Bejárja a container tömböt és egyesével meghívja az ott lévő objektumok callTimer() fv-ét
      this.container.each(function(element) {
        element.callTimer(d);
      });
      // Befejeződött a tömb bejárása
      this.isRun = false;
    }
  }
}

// Várakoztató "osztály". Több ilyen is lehet. Akkor kerül egy "várakoztatóba" két objektum,
// ha a második csak akkor kezdődhet el számolni, ha az előző már lefutott
var Delayer = Class.create({
  // Mikor kezdett el várakozni a this.current elem.
  delayStart: null,
  // Az aktuálisan várakozó objektum
  current: null,
  // Az objektum lista, ami várakozik
  container: [],
  // Éppen várakozik-e vmi
  isRun: false,
  name: null,

  initialize: function(name) {
    this.name = name;
  },

  start: function(element) {
    this.container[this.container.length] = element;
    Timer.add(this, false);
  },

  // Eltávolít egy várakozó objektumot
  cancel: function(element) {
    this.container = this.container.without(element);
  },

  // Alaphelyzetbe állítja a "várakoztatót"
  reset: function(element) {
    // Törli az elemet a listáról
    if(typeof(element)!='undefined') {
      this.cancel(element);
    }
    this.current = null;
    this.delayStart = null;
    if(document.test) {
      document.test.logta.value+="----- Reset: "+this.name+"\n";
    }
  },

  // Eggyel továbblépteti a várakozó elemek listáján.
  next: function(d) {
    // Ha nincs több elem, akkor kilép
    if(this.container.length==0) {
      this.reset();
      return false;
    }

    this.current = this.container.first();
    this.delayStart = d;
    return true;
  },

  callTimer: function(d) {
    // Ha az aktuális elem null és nincs tovább
    if(this.current==null && !this.next(d)) {
      Timer.remove(this);
      return false;
    }
    document.test.logta.value+=d.getTime()+": "+this.name+" ("+this.delayStart.getTime()+") ["+this.current.n+"]\n";

    // Ennyi másodpercet kell várnia az adott objektumnak
    w        = this.current.wait;
    // Ennyi másodperc van még hátra
    distance = w-parseInt((d.getTime()-this.delayStart.getTime())/1000)-1;
    if(distance>0) {
      this.current.status('Hátra van:'+distance+' s');
    } else {
      this.current.run(d);
      this.reset(this.current);
    }
  },
});

var Obj = Class.create({
  // Az objektum sorszáma
  n: 0,
  // Ennyi másodpercet várjon
  wait: 5,

  // ahova írkálnia kell
  html: null,

  initialize: function(n) {
    this.n = n;
    this.html = new Element('div', {'id': 'obj_'+n}).update('init');
    document.body.appendChild(this.html);
  },

  run: function(d) {
    this.status('Kész: '+this.n);
  },

  status: function(msg) {
    this.html.update(msg);
  }
});

var counter = 0;
var d1 = new Delayer('d1');
function startDelay1() {
  document.test.logta.value+="+++++ Klikk: Delayer1\n";
  d1.start(new Obj(counter));
  counter++;
}
var d2 = new Delayer('d2');
function startDelay2() {
  document.test.logta.value+="+++++ Klikk: Delayer2\n";
  d2.start(new Obj(counter));
  counter++;
}
</script>
</head>

<body>
<script>
document.body.onload = Timer.start();
</script>
<form name="test">
<textarea name="logta" cols="100" rows="40" style="float: right"></textarea>
</form>
<div id="message" onclick="javascript: startDelay1()">Delayer1</div>
<div id="message2" onclick="javascript: startDelay2()">Delayer2</div>
</body>
</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:

<html>
<head>
<script language="javascript" src="prototype.js"></script>
<script type="text/javascript">
var Timer = {
  container:  [],

  add: function(element) {
    this.container[this.container.length] = element;
  },

  remove: function(element) {
    this.container = this.container.without(element);
  },

  start: function(event) {
    window.setInterval('Timer.run()', 500);
  },

  run: function() {
    d = new Date();
    this.container.each(function(element) {
      element.callTimer(d);
    });
  }
}

var Delayer = Class.create({
  delayStart: null,
  current: null,
  container: [],
  // Éppen várakozik-e vmi
  isRun: false,
  name: null,

  initialize: function(name) {
    this.name = name;
  },

  start: function(element) {
    this.container[this.container.length] = element;
    Timer.add(this);
  },

  cancel: function(element) {
    this.container = this.container.without(element);
  },

  reset: function(element) {
    // Törli az elemet a listáról
    if(typeof(element)!='undefined') {
      this.cancel(element);
    }
    this.current = null;
    this.delayStart = null;
    if(document.test) {
      document.test.logta.value+="----- Reset: "+this.name+"\n";
    }
  },

  next: function(d) {
    // Ha nincs több elem, akkor kilép
    if(this.container.length==0) {
      this.reset();
      return false;
    }

    this.current = this.container.first();
    this.delayStart = d;
    return true;
  },

  callTimer: function(d) {
    // Ha az aktuális elem null és nincs tovább
    if(this.current==null && !this.next(d)) {
      Timer.remove(this);
      return false;
    }
    document.test.logta.value+=d.getTime()+": "+this.name+" ("+this.delayStart.getTime()+") ["+this.current.n+"]\n";

    w        = this.current.wait;
    distance = w-parseInt((d.getTime()-this.delayStart.getTime())/1000)-1;
    if(distance>0) {
      this.current.status('Még vár:'+distance+' s');
    } else {
      this.current.start(d);
      this.reset(this.current);
    }
  }
});

var Obj = Class.create({
  // Objektum sorszáma
  n: 0,
  // Ennyi másodpercet várakozzon
  wait: 5,
  // Ebben írkáljuk, hogy mi van
  html: null,

  initialize: function(n) {
    this.n = n;
    this.html = new Element('div', {'id': 'obj_'+n}).update('init');
    document.body.appendChild(this.html);
  },

  start: function(d) {
    this.status('Kész: '+this.n+'. elem');
  },

  status: function(msg) {
    this.html.update(msg);
  }
});

Event.observe(window, 'load', Timer.start);

var counter = 0;
var d1 = new Delayer('d1');
function startDelay1() {
  document.test.logta.value+="+++++ Klikk: d1\n";
  d1.start(new Obj(counter));
  counter++;
}
var d2 = new Delayer('d2');
function startDelay2() {
  document.test.logta.value+="+++++ Klikk: d2\n";
  d2.start(new Obj(counter));
  counter++;
}
</script>
</head>

<body>
<form name="test">
<textarea name="logta" cols="100" rows="40" style="float: right"></textarea>
</form>

<div id="message" onclick="javascript: startDelay1()">Delay 1.</div>
<div id="message2" onclick="javascript: startDelay2()">Delay 2.</div>

</body>
</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:

a1 = new A();
b1 = new A();

a1.x = 1;
b1.x = 2;

alert(a1.x) //--> 1 helyett 2!!!
alert(b1.x) //--> 2

// majd megint lefuttatjuk
a1.x = 3;
b1.x = 4;

// És most vmiért működik o_O
alert(a1.x) //--> 3
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 = []; )

var Delayer = Class.create({
  // ...
  // Ezt továbbra is így hagyom, mert "így helyes"
  container: [],
  // ...

  initialize: function(name) {
    this.name = name;
    // Az alábbi sor került beszúrásra
    this.container = [];
  },
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:

document.body.onload = Timer.start();
ez így nem onload -ra fog lefutni:) így igen:

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.