[JS] Event handlers, hoe eindeloze recursie voorkomen?

Pagina: 1
Acties:

Acties:
  • 0 Henk 'm!

  • Rekcor
  • Registratie: Februari 2005
  • Laatst online: 05-09 21:08
Ik heb twee objecten die naar elkaar luisteren. Als object A verandert, moet er iets in object B gebeuren; verandert er iets in B, dan moet er ook iets in A gebeuren.

Simpele voorstelling van zaken:
JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* object A */

objA.setSomeValue = function(val){
 if (this.val==val) return;
 this.val=val;

 this.onChange(val);
}

objA.onChange = function(val){
 objB.setSomeValue(val)
}

/* Object B */

objB.setSomeValue = function(val){
 if (this.val==val) return;
 this.val=val;
}

objB.onChange = function(val){
 objA.setSomeValue(val);
}


(objA en objB zijn gekoppeld aan twee user interface componenten, die beide door de gebruiker bediend kunnen worden).

Wat ik nu wil is dat 'this.onChange(val);' alleen aangeroepen wordt zodra de gebruiker iets verandert in de gerelateerde user interface component, en niet zodra de functie call van het andere object afkomstig is.

Dat kan natuurlijk eenvoudig met een extra parameter:

JavaScript:
1
2
3
4
5
6
7
8
9
10
/* object A */

objA.setSomeValue = function(val, byUser){
 if (this.val==val) return;
 this.val=val;

 if (byUser) {
  this.onChange(val);
 }
}

HTML:
1
<button onclick="objA.setSomeValue(this.value, true)"></button>


Maar misschien weten jullie een elegantere oplossing...?

P.S. De oplettende lezer zal merken dat "if (this.val==val) return;" in principe al een beveiliging is tegen een oneindige loop, maar in de praktijk verandert val - door afronding - steeds met een infinitesimale hoeveeldheid.

Acties:
  • 0 Henk 'm!

  • NetForce1
  • Registratie: November 2001
  • Laatst online: 13-09 13:16

NetForce1

(inspiratie == 0) -> true

Rekcor schreef op vrijdag 20 augustus 2010 @ 16:05:
P.S. De oplettende lezer zal merken dat "if (this.val==val) return;" in principe al een beveiliging is tegen een oneindige loop, maar in de praktijk verandert val - door afronding - steeds met een infinitesimale hoeveeldheid.
Dan bouw je in die check toch ook nog een marge in van zeg 0.00001 oid.

De wereld ligt aan je voeten. Je moet alleen diep genoeg willen bukken...
"Wie geen fouten maakt maakt meestal niets!"


Acties:
  • 0 Henk 'm!

  • Rekcor
  • Registratie: Februari 2005
  • Laatst online: 05-09 21:08
Ik heb al een oplossing: in het object bijhouden of een bepaald event al bezig is uitgevoerd te worden:

JavaScript:
1
2
3
4
5
6
7
objA.onChange = function(val){
 if (!this.onChangeIsBeingCalled){
  this.onChangeIsBeingCalled = true;
  objB.setSomeValue(val)
 }
 this.onChangeIsBeingCalled = false;
} 

Acties:
  • 0 Henk 'm!

  • Bosmonster
  • Registratie: Juni 2001
  • Laatst online: 10-09 08:45
In principe zou het meermaals aanroepen van de events nooit tot problemen moeten mogen leiden als het goed gemaakt is.

Wat je nu doet is potentieel gevaarlijker, aangezien je nu triggers gaat missen, wat hele rare bugs op kan leveren.

Als het al een issue is kun je dus beter voor queue'ing gaan.

[ Voor 19% gewijzigd door Bosmonster op 20-08-2010 17:00 ]


Acties:
  • 0 Henk 'm!

  • Klaasvaak
  • Registratie: Maart 2010
  • Laatst online: 12-09 21:10
Als de functie 'onchange' alleen bij gebruikersinvoer dient aangeroepen te worden, kan je deze dan niet beter vanuit een eventhandler aanroepen inplaats vanuit de functie 'setSomeValue' ?


JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
objA.setSomeValue = function(val){
 this.val = val;
};
objA.onChange = function(val){
 objB.setSomeValue(val);
};
objA.buttonHandler = function(val){
 this.setSomeValue(val);
 this.onChange(val);
};

objB.setSomeValue = function(val){
 this.val = val;
};
objB.onChange = function(val){
 objA.setSomeValue(val);
};
objB.buttonHandler = function(val){
 this.setSomeValue(val);
 this.onChange(val);
};

<button onclick="objA.buttonHandler(this.value)"></button>

Acties:
  • 0 Henk 'm!

  • Rekcor
  • Registratie: Februari 2005
  • Laatst online: 05-09 21:08
Bosmonster schreef op vrijdag 20 augustus 2010 @ 16:59:
Wat je nu doet is potentieel gevaarlijker, aangezien je nu triggers gaat missen, wat hele rare bugs op kan leveren.
Maar het mag toch nooit zo zijn dat event A, event B triggered en event B weer A? Zou je een voorbeeld kunnen noemen van een scenario waarin het fout gaat?
Klaasvaak schreef op zaterdag 21 augustus 2010 @ 15:57:
Als de functie 'onchange' alleen bij gebruikersinvoer dient aangeroepen te worden, kan je deze dan niet beter vanuit een eventhandler aanroepen inplaats vanuit de functie 'setSomeValue' ?
Goed idee, maar ik vind het niet juist om 'onChange' alleen in de buttonhandlerfunctie aan te roepen. Het veranderen gebeurt namelijk niet daar, maar in de setSomeValue. Daarnaast wil ik soms juist wel dat onChange wordt aangeroepen, ook als de gebruiker niet op een knop drukt. Het moet alleen geen oneindige loop worden. Dus misschien toch niet zo'n goed idee :)

Acties:
  • 0 Henk 'm!

  • Bosmonster
  • Registratie: Juni 2001
  • Laatst online: 10-09 08:45
Rekcor schreef op maandag 23 augustus 2010 @ 12:59:
[...]


Maar het mag toch nooit zo zijn dat event A, event B triggered en event B weer A? Zou je een voorbeeld kunnen noemen van een scenario waarin het fout gaat?
Dan is dus punt 1 van toepassing: je applicatie heeft een bug, niet je eventhandler :P

Acties:
  • 0 Henk 'm!

  • Rekcor
  • Registratie: Februari 2005
  • Laatst online: 05-09 21:08
Bosmonster schreef op maandag 23 augustus 2010 @ 13:07:
[...]


Dan is dus punt 1 van toepassing: je applicatie heeft een bug, niet je eventhandler :P
Ja, en die bug fix ik toch :) ?

Hoe wilde je het anders doen met twee objecten die elkaar kunnen updaten?

Acties:
  • 0 Henk 'm!

  • Feanathiel
  • Registratie: Juni 2007
  • Niet online

Feanathiel

Cup<Coffee>

Is het niet mogelijk om een abstractielaag toe te voegen? De knop roept een functie/methode van de nieuwe klasse aan en speelt vervolgens de waarden door naar andere geïnteresseerden (in dit geval objA en objB).

Acties:
  • 0 Henk 'm!

  • Bosmonster
  • Registratie: Juni 2001
  • Laatst online: 10-09 08:45
De objecten een update-method geven en niet de eventhandlers hiervoor misbruiken.

Overigens heb ik zelf redelijk recent een complex formulier ontwikkeld waar ik tegen hetzelfde aanliep :) Alle formulierevelden (enkele honderden) hadden allemaal mogelijke uitwerking op andere velden en die weer op andere, etc. Eén wijziging kon dus een waterval aan andere wijzigingen en restricties oproepen.

Dit kreeg ik niet opgelost met de gebruikelijke recursie, omdat dat redelijk vaak in oneindige loops terecht zou komen.

Uiteindelijk heb ik de state van het formulier opgeslagen. Bij wijzigingen in het formulier verandert die state en een update-method liep vervolgens die state na aan de hand van een rule-set om het formulier aan te passen.

Dit had naast performance ook als bijkomend voordeel dat ik een eenvoudige plaats had waar ik de rules kon beheren, onafhankelijk van je code. Wel zo makkelijk met de honderden wijzigingen en fixes die er later nog binnenkwamen :+ En wel zo netjes om dat soort rules te scheiden van je applicatie.

Had overigens ook nog een ander leuk voordeel, namelijk dat ik eenvoudig vanuit een bestaande dataset het hele formulier in 1x goed kon zetten. De state zelf en data uit de backend was allemaal JSON namelijk.

[ Voor 97% gewijzigd door Bosmonster op 23-08-2010 14:32 ]

Pagina: 1