JavaScript addEventListener aufräumen

Freezedevil

Lieutenant
Registriert
Mai 2011
Beiträge
641
Servus,

ich erzeuge mir per JS Elemente einer unordered list und möchte an jedes Element einen Funktionsaufruf hängen, welcher unter anderem den Index enthält. Der erste Ansatz war folgender:

playlist: Array von Objekten die im Wesentlichen nur die Namen der Einträge enthalten
list: ist nach der Zuweisung ein <ul> Element
Rest sollte ersichtlich sein.
Code:
function buildPlaylist (playlist) {
	list = document.getElementById('playlist');
	list.innerHTML = "";
	for (i=0; i<playlist.length; ++i) {
		item = document.createElement('li');
		item.className = "playlistItem";
		item.appendChild(document.createTextNode(playlist[i].label));
		item.addEventListener('click', function(){playerAction({"action": "playlist", "item": i})}, false);
		list.appendChild(item);
	}
}

function playerAction(action) {
	...
	switch (action.action) {
		case "playlist":
			//play specific item in current playlist
			console.log(action.item);
			break;
		...
	}
}
Das Problem hierbei ist jedoch, dass ich unabhängig von dem Eintrag auf den ich klicke immer die selbe Zahl bekomme (immer die Länge der Liste). Das legt die Vermutung nahe, dass hier call-by-reference angesagt ist, wodurch das Ergebnis nachvollziehbar wäre.

Aus diesem Grund habe ich mir folgende Lösung zurecht geschustert:

Code:
function buildPlaylist (playlist) {
	list = document.getElementById('playlist');
	list.innerHTML = "";
	for (i=0; i<playlist.length; ++i) {
		item = document.createElement('li');
		item.className = "playlistItem";
		item.appendChild(document.createTextNode(playlist[i].label));
		item.index = i;
		item.addEventListener('click', help, false);
		list.appendChild(item);
	}
}

function help (e) {
	i = e.srcElement.index;
	playerAction({"action": "playlist", "item": i});
}

playerAction identisch

Ich finde es jedoch unglaublich hässlich dafür extra noch eine Funktion zu spendieren und wollte daher wissen ob jemand von euch einen eleganteren Weg sieht das Problem zu lösen.
Danke für eure Antworten.
 
Code:
function buildPlaylist (playlist) {
    list = document.getElementById('playlist');
    list.innerHTML = "";
    for (i=0; i<playlist.length; ++i) {
        item = document.createElement('li');
        item.className = "playlistItem";
        item.appendChild(document.createTextNode(playlist[i].label));
        item.addEventListener('click', function(e) {
            i = e.srcElement.index;
            playerAction({"action": "playlist", "item": i});}, false);
        list.appendChild(item);
    }
}

function playerAction(action) {
    ...
    switch (action.action) {
        case "playlist":
            //play specific item in current playlist
            console.log(action.item);
            break;
        ...
    }
}

:D

Wobei ich sagen muss dass ich deine zweite Lösung am elegantesten finde.
 
Im Moment referenziert dein i in Zeile 8 immer das i in Zeile 4 und das ändert sich eben mit jedem Schleifendurchlauf. Du musst eine Closure erzeugen, sodass sich das i nicht bei allen Handlern auf den gleichen (neuesten und damit größten) Wert bezieht.

Das geht am einfachsten, indem du den Aufruf von addEventListener in eine eigene Funktion auslagerst, sodass diese neue Funktion dann über i abschließt. item.index und sowas kannst du dir dann sparen. Die Funktion kannst du in buildPlaylist definieren, damit sie dir nicht den äußeren Namespace zumüllt.

Beispiel:

Code:
// Parameter extra doof benannt, damit klarer wird, was hier passiert
function listenerHelper(a, b) {
  a.addEventListener('click', function() {
    playerAction(..., b);
  });
}

for (var i = 0; ...) {
  // ...
  listenerHelper(item, i);
  // ...
}

Das b in Zeile 4 kommt aus Zeile 2. Beim Aufruf von listenerHelper (Zeile 10) wurde eine Closure erzeugt, sodass sich dieser Wert nicht mehr ändert.

Außerdem fehlt das "var" bei der Deklaration von i. Nicht vergessen!
 
Zuletzt bearbeitet:
So geht es nicht, da du item ja gar keinen index zugewiesen hast den du in Zeile 9 auslesen könntest.
Ich habs jetzt so gemacht:

Code:
function buildPlaylist (playlist) {
	list = document.getElementById('playlist');
	list.innerHTML = "";
	for (i=0; i<playlist.length; ++i) {
		item = document.createElement('li');
		item.className = "playlistItem";
		item.appendChild(document.createTextNode(playlist[i].label));
		item.index = i;
		item.addEventListener('click', function(e){
			playerAction({"action": "playlist", "item": e.srcElement.index})}, false);
		list.appendChild(item);
	}
}
Lustigerweise hatte ich die Idee in der Form auch schon, hab sie vor der Umsetzung allerdings verworfen. Evtl. sollte ich langsam Schluss für heute machen.

Mit dem Ergebnis kann ich in der Form leben, jedoch würde es nicht schaden noch Alternativen zu hören wenn jemanden was unter den Nägeln brennt ^^

Edit: Die Antwort bezog sich nur auf UltiSalamander

@character: Das ist defintiv auch ne nette Lösung. Ich bau es mal testweise ein und entscheide dann was mit besser gefällt :)
Bisher ist auch irgendwie an mir vorbei gegangen wann ich das var brauche und wann nicht. Ich konnte bisher noch keinen Unterschied feststellen und lasse es daher immer weg. Wär cool wenn du mir den konkreten Nutzen näher bringen könntest.

@Tumbleweed: Ich schieb schon ewig vor mir her mich endlich mal mit JQuery zu beschäftigen, daher scheidet das für den Moment aus. Da deine Lösung aber am schönsten ist nehme ich mir auf jeden Fall vor ab meinem nächsten Projekt JQuery einzusetzen. Ich hoffe das erspart mir dann so manche JS-Qual.

Ich danke euch allen.
 
Zuletzt bearbeitet:
Freezedevil schrieb:
Bisher ist auch irgendwie an mir vorbei gegangen wann ich das var brauche und wann nicht. Ich konnte bisher noch keinen Unterschied feststellen und lasse es daher immer weg. Wär cool wenn du mir den konkreten Nutzen näher bringen könntest.
Wenn du var weglässt (oder vergisst), erzeugst du eine globale Variable (also eine Property am window-Objekt). Das kann zu widerlichen Bugs führen, die sich nur schwer finden lassen.

Code:
function test() {
  a = 1;
  var b = 2;
}

test();

console.log(a); // -> 1
console.log(window.a); // -> 1
console.log(b); // -> ReferenceError: b is not defined
console.log(window.b); // -> undefined
 
Auch wenn du bestimmt schon eine Lösung gefunden hast, möchte ich dir noch diese hier vorstellen!
Einfach, weil sie irgendwie cooler aussieht ;)


Code:
item.addEventListener('click', function(){playerAction({"action": "playlist", "item": i})}, false);
wird zu
Code:
item.addEventListener('click', (function(n){return function(){ playerAction({"action": "playlist", "item": n});} })(i),false);
 
Zurück
Oben