EventListener schachteln für click/keyup-Behandlung

- javascript
0 Nico R.
0 Gunnar Bittersmann
0 Nico R.
0 Rolf B
0 Nico R.
0 Nico R.
0 Rolf B
0 Rolf B
0 Rolf B
0 Felix Riesterer
0 Nico R.
Hallo zusammen,
ich komme gerade nicht weiter. Das Dialogfenster im Beispielscript kann man per Mausklick auf OK oder per Enter-Taste schließen (und dabei Daten absenden), siehe Konsolenausgabe. Nutze ich nur den Mausklick oder nur Enter, ist alles in Ordnung. Wenn ich aber z.B. den dialog beim ersten Mal per Enter schließe und beim zweiten Mal per click, wird der click zweimal ausgeführt.
button.addEventListener("click", function(event){
dialog.showModal();
dialog_ok.addEventListener("click", function(event) {
console.log("dialog klick OK");
dialog.close();
}, {once: true} );
dialog.addEventListener("keydown", function(event) {
console.log("dialog keydown");
if(event.keyCode == 13) {
console.log("dialog keydown OK");
event.preventDefault();
dialog.close();
}
}, {once: true} );
});
Bei anderen Kombinationen wirds richtig wild... Das Ganze überschlägt und summiert sich vermutlich, weil ich mit jedem Aufruf des dialog-Fensters per button auch die Event-Handler für click und clack mehrfach registriere. Das hatte ich versucht über die Option once zu verhindern, aber das hatte nicht den erhofften Effekt.
Am einfachsten lässt sich das Problem im Beispielscript beheben, indem ich die beiden inneren EventListener außerhalb des button-EventListeners packe.
Aber gibt es auch eine Lösung, ohne die beiden auszulagern?
Schöne Grüße
Nico
Wie das so ist... Wenn man das Problem aufgeschrieben hat, kommt einem manchmal die Lösung. Diese hier - per Schalter - ist nicht gerade elegant, hilft mir aber erstmal:
let erstaufruf = true;
button.addEventListener("click", function(event){
dialog.showModal();
if(erstaufruf) {
dialog_ok.addEventListener("click", function(event) {
console.log("dialog klick OK");
dialog.close();
});
}
if(erstaufruf) {
dialog.addEventListener("keydown", function(event) {
console.log("dialog keydown");
if(event.keyCode == 13) {
console.log("dialog keydown OK");
event.preventDefault();
dialog.close();
}
});
}
erstaufruf = false;
});
Gibts das auch in schöner?
Schöne Grüße
Nico
@@Nico R.
Gibts das auch in schöner?
Ich denke, schon.
Man kann Eventlistener auch wieder entfernen.
Aber warum registstrierst du die überhaupt im Eventhandler? Wenn du die da rausnimmst, sollte sich das Problem erledigt haben.
Ach, und noch was:
if(event.keyCode == 13) {
Ist 13 deine Glückszahl oder was hat die zu bedeuten? Das ist unleserlicher Code. Außerdem ist keyCode
veraltet.
Jolan tru
Hallo Gunnar,
Man kann Eventlistener auch wieder entfernen.
Hm, ja. Entfernen will ich sie ja nicht, sondern nur einmalig registrieren.
Aber warum registstrierst du die überhaupt im Eventhandler? Wenn du die da rausnimmst, sollte sich das Problem erledigt haben.
Das ist in dem Script damals so entstanden. Ich müsste, wenn ich die beiden Funktionen auslagere, noch mehr auslagern bzw. stehen dann bestimmte Parameter nicht mehr zur Verfügung usw. Es wäre ein größerer Umbau, da ist die Lösung mit dem Schalter jetzt das einfachste.
Ist 13 deine Glückszahl oder was hat die zu bedeuten? Das ist unleserlicher Code. Außerdem ist
keyCode
veraltet.
Wieso unleserlich? Die 13 war der keyCode für Enter. Hat bis dato auch funktioniert. Ich habs aber jetzt auf das einfachere und verständlichere key geändert. Danke für den Hinweis.
Schöne Grüße
Nico
Hallo Nico R.,
bzw. stehen dann bestimmte Parameter nicht mehr zur Verfügung
Was soll da denn fehlen? Im Moment stehen sie innerhalb des click-Handlers vom Button. Der kriegt eh keine Parameter. Oder generierst Du in diesem Handler Daten, die die Funktionen beim Auslösen des close benötigen?
Ist der reale Button irgendwo auf der Homepage in Gebrauch, dass man sich das angucken könnte?
Rolf
Hallo Rolf,
nee, das ist das Redaktionssystem. Schwer zu erklären. Innerhalb dieser anonymen Funktion passiert noch viel mehr. Es gibt dort auch wiederum Funktionsdeklarationen usw.
Ich denke/dachte mir, dass es relativ egal ist, ob ich eine Funktion mit function funktionsname() { } deklariere und dann mit addEventlistener("click", funktionsname) aufrufe oder ob ich direkt die anonyme Funktion als Kapselung nutze. Bisher kam ich damit gut zurecht, das hab ich mir irgendwie so angewöhnt.
Im Zuge dieses Konstrukts bin ich aber gerade auf ein neues Problem gestoßen, hier im neuen Beispielscript zu sehen. Sagen wir mal, es gibt jetzt innerhalb der anonymen Funktion eine globale Variable val, die aus einem input-Feld außerhalb des dialogs kommt. Beim ersten Absenden (Nummer eingeben => dialog öffnen => OK) wird val korrekt ausgegeben, bei allen folgenden ändert es aber den Wert nicht mehr:
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
</head>
<body>
<input type="number" min="1" max="10">
<button id="button">Dialog öffnen</button>
<dialog id="dialog">
<button id="dialog_ok" type="button">OK</button>
<button id="dialog_abbr" type="reset">Abbrechen</button>
</dialog>
<script>
(function () {
const button = document.getElementById("button");
const dialog = document.getElementById("dialog");
const dialog_ok = document.getElementById("dialog_ok");
const dialog_abbr = document.getElementById("dialog_abbr");
var erstaufruf = true;
button.addEventListener("click", function(event){
const val = document.querySelector("input").value;
dialog.showModal();
if(erstaufruf) {
dialog_ok.addEventListener("click", function(event) {
console.log("dialog klick OK");
absenden();
dialog.close();
});
}
if(erstaufruf) {
dialog.addEventListener("keydown", function(event) {
console.log("dialog keydown");
if(event.keyCode == 13) {
console.log("dialog keydown OK");
event.preventDefault();
absenden();
dialog.close();
}
});
}
erstaufruf = false;
console.log("Testausgabe val vor absenden(): "+val);
function absenden() {
console.log("Senden: "+val);
}
});
})();
</script>
</body>
</html>
Die Funktion absenden() ist auch innerhalb der anonymen Funktion definiert und soll eigentlich immer auf den aktuellen val zugreifen. Sie nutzt aber immer das val vom ersten absenden. Die Testausgabe direkt vor function ausgabe() liefert den korrekten, aktuellen val, nur die Funktion selbst dann nicht mehr.
Schuld ist die Schalterzeile if(erstaufruf) { Ich verstehe nur noch nicht ganz, warum sie function absenden() beeinflusst. Es wird anscheinend immer der Aufruf absenden() vom ersten Aufruf verwendet. Ist das logisch, ja? Muss ja...
Es wird wohl Zeit, Feierabend zu machen...
Schöne Grüße
Nico
Gunnars Vorschlag mit removeEventListener() war doch der richtige Ansatz. Ich habe dafür aus den anonymen Funktionen im addEventListener() benannte Funktionen gemacht und entferne nach dem Aufruf den EventListener direkt wieder. So konnte auch der dämliche Schalter wieder raus.
Danke für die Hilfe und schöne Grüße
Nico
Hallo Nico R.,
welcome in closure hell…
Für die erklärende Antwort brauche ich aber meinen PC, bin gerade am Handy. Kommt demnächst…
Rolf
Hallo Nico R.,
oha, ich hab mein Versprechen vergessen. Aber wie ich Felix unten erklärt habe, glaube ich nicht, dass es Dich wirklich interessiert.
Grundsätzlich ist es so, dass bei jedem Aufruf des Öffnen-Handlers ein neuer Ausführungskontext für diese Funktion entsteht. Die Variable val und auch die anonymen Funktionen, die Du an die Dialogbuttons bindest, sind an diesen Ausführungskontext gekoppelt.
Wenn Du die Handler für die Dialogbuttons nur beim ersten Klick auf Öffnen registrierst, greifen sie für alle Zeit auf die val-Instanz dieses ersten Öffnens zu.
Wenn man Handler nur einmal registriert, muss man aufpassen, wie und wo man seine Arbeitsdaten speichert, damit alle Handler auf den gleichen Datensatz zugreifen. Dazu schreibe ich aber erst dann einen Aufsatz, wenn Du mehr über den tatsächlichen Code erzählst, in dessen Umfeld das abläuft. Eine Online-Testinstanz deiner Seite, ohne schützenswerte persönliche Daten, würde hier helfen.
Rolf
Hallo Rolf,
oha, ich hab mein Versprechen vergessen. Aber wie ich Felix unten erklärt habe, glaube ich nicht, dass es Dich wirklich interessiert.
doch 😉 Ich hatte jetzt etwas Zeit, und hab das Codestück mal extrahiert und testtauglich umgeschrieben: https://fsv-optik.de/tests/dialog_fetch.html
Hier liegen alle weiteren Definitionen und Funktionen innerhalb der click-Funktion document.querySelector("tbody").addEventListener("click", function(event) { })
Beim Klick auf eine Tabellenzelle wird der dialog geöffnet, über den man den Wert der Tabellenzelle als input-Feld angezeigt bekommt und absenden kann (Anzeige über Entwicklerwerkzeuge -> Netzwerk).
Hier entsteht dann auch das oben beschriebene Phänomen der „Aufsummierung“ an fetch-Absendungen, die man bestaunen kann, wenn man die beiden Zeilen dialog_ok.removeEventListener("click"...)
und dialog.removeEventListener("keydown"...)
auskommentiert.
Es werden in dieser Variante auch alle click-Handler für den Dialog bei jedem Öffnen des Dialogs neu registriert. Das ist natürlich auch quatsch. Deshalb hab ich eine zweite Variante gebaut: https://fsv-optik.de/tests/dialog_fetch.html
Hier liegen die Unterfunktionen nicht mehr innerhalb des Tabellenzellen-clicks. Stattdessen gibt es die globalen Variablen let input, id, feld, wert
, auf die die Unterfunktionen zugreifen (global stimmt nicht ganz, da das Ganze in einer Kapselfunktion liegt).
Ob das das Gelbe vom Ei ist, weiß ich nicht. Auf jeden Fall funktioniert diese Variante ohne RemoveEventListener. Für Kritik und Anregungen bin ich auf jeden Fall offen.
Schöne Grüße
Nico
Lieber Nico,
Ob das das Gelbe vom Ei ist, weiß ich nicht.
da kann ich Dir weiterhelfen: Es ist noch nicht das Gelbe vom Ei.
<tbody>
-Elemente sind keine interaktiven Elemente. Du solltest sie auch nicht mit einem EventListener interaktiv machen. Dafür gibt es besser geeignete Elemente wie z.B. das <button>
-Element.Auf jeden Fall funktioniert diese Variante ohne RemoveEventListener.
Mir scheint das der identische Link zu sein. Eine zweite Variante konnte ich nicht finden. Die vorliegende fügt und entfernt jedenfalls fröhlich ihre EventListener hin und her.
Liebe Grüße
Felix Riesterer
Hallo Felix,
Mir scheint das der identische Link zu sein. Eine zweite Variante konnte ich nicht finden. Die vorliegende fügt und entfernt jedenfalls fröhlich ihre EventListener hin und her.
*kreisch* Ja, wie blöd. Hier der richtige Link: https://fsv-optik.de/tests/dialog_fetch_neu.html
<tbody>
-Elemente sind keine interaktiven Elemente. Du solltest sie auch nicht mit einem EventListener interaktiv machen. Dafür gibt es besser geeignete Elemente wie z.B. das<button>
-Element.
Hm, ja. Klingt erstmal logsich. Aber wie sähe das in der Praxis aus? In jeder bearbeitbaren td ein button… Und worauf registriere ich dann den EventListener? Ich habe hier gelernt, es ist bessere Praxis, an einem übergeordneten Element zu lauschen, statt auf jedem einzelnen (button)-Element einen Handler zu registrieren.
- Wenn Du eine Kapselfunktion benötigst, warum genügt Dir dann nicht diejenige (anonyme) Funktion, die mit dem DOMContentLoaded-Ereignis ausgeführt wird? Diese doppelte Kapselung nur um der Kapselung willen erscheint mir Unsinn zu sein.
Das stimmt. Die hatte ich noch drin, weil es im Orginalscript weitere "Unterscripts" gibt.
- Wenn ich den Inhalt einer Tabellenzelle editieren soll, dann erwarte ich, dass im Eingabefeld der aktuell dort vorhandene Wert bereits eingetragen steht. Bei den Datumswerten ist das nicht der Fall.
Das hatte ich beim Entkernen mit entfernt. Ist jetzt im neuen Script wieder drin.
- Es ist in JavaScript zwar möglich, Funktionen irgendwo im Code zu definieren, es ist aber für ein späteres Überarbeiten sehr hilfreich, wenn alle wesentlichen Funktionen der Reihe nach definiert werden, damit man sie im Code besser findet.
Ich hoffe das ist in der neuen Variante etwas besser gelungen.
Schöne Grüße
Nico
@@Nico R.
Und worauf registriere ich dann den EventListener? Ich habe hier gelernt, es ist bessere Praxis, an einem übergeordneten Element zu lauschen, statt auf jedem einzelnen (button)-Element einen Handler zu registrieren.
Ja. Das nennt sich event delegation.
Den Eventhandler musst du nicht ganz oben document.documentElement
registrieren; das Komponentenelement ist eine gute Stelle dafür.
Von der Komponente könnte es mehrere Instanzen auf einer Seite geben, deshalb gehe ich diese ich in meinem Beispiel in einer Schleife durch. Wenn die Komponente garantiert einzig ist, brauchst du die Schleife nicht.
Die Konsolenausgaben zeigen an, welches Element jeweils geclickt wurde. Da siehst du, dass es nicht das button
-Element ist, wenn du auf ‚Action!‘ clickst. Deshalb muss man sich von event.target
aus im DOM mit closest()
bis zum button
hochhangeln.
Wenn es keinen solchen gibt bis man ganz oben angekommen ist, ist die Bedingung nicht erfüllt und die Aktion wird nicht ausgefüht. Den else
-Zweig wirst du nicht brauchen.
Jolan tru
Hallo Gunnar,
Den Eventhandler musst du nicht ganz oben
document.documentElement
registrieren; das Komponentenelement ist eine gute Stelle dafür.Von der Komponente könnte es mehrere Instanzen auf einer Seite geben, deshalb gehe ich diese ich in meinem Beispiel in einer Schleife durch. Wenn die Komponente garantiert einzig ist, brauchst du die Schleife nicht.
Der Link zu deinem Beispiel funktioniert nicht. Ich musste die Adresse von Hand reinkopieren. Hier nochmal für andere Mitleser: https://codepen.io/gunnarbittersmann/pen/bNGywOV?editors=1011
Das Prinzip im Beispiel nutze ich in anderen Scripts auch. Die Anwendung für meinen Fall ist mir noch nicht ganz klar. Oder ich hab was falsch verstanden.
An welcher Stelle würde sich denn in meiner Tabelle das Komponentenelement befinden? Das kann doch eigentlich nur innerhalb von td sein. Aber dann brauche ich ja kein extra Element, sondern kann direkt die buttons in einer Schleife durchgehen und registrieren. Das wäre dann ohne event delegation. Die wäre ja aber bei einem extra eingefügten div ohnhin überflüssig, denn eine Schleife brauchts ja trotzdem.
Schöne Grüße
Nico
Hallo Nico,
den geeignete Platz, um auf das Event zu lauschen, findest Du so:
D.h. bei Dir das thead-Element der tabelle. Die Table als solche ginge auch, weil Du im Table Header keine Buttons hast.
Die Frage nach mehreren Komponenten stellt sich bei Dir nicht, du hast nur eine Tabelle.
Falls Du mehrere gleichartige Tabellen auf der Seite hättest, die Edit-Buttons besitzen, dann hast Du zwei Optionen.
Der Unterschied liegt darin, dass Du bei einem Eventhandler pro Tabelle die Möglichkeit hast, diesen Eventhandler so zu gestalten, dass er nur mit dieser einen Tabelle hantiert und ggf. über eine Closure die passenden Kontextdaten zur Tabelle hat.
Wenn Du einen Eventhandler für mehrere Tabellen baust, muss der Eventhandler erstmal rausfinden, um welche Tabelle es geht.
Aber, wie gesagt, du hast nur eine Tabelle, deswegen ist diese Überlegung unnötig. Leg den click-Handler auf die Tabelle.
Wenn Du nur einen Button hast, brauchst Du keine weiter Prüfung und kannst im click-Event direkt mit event.target.closest("tr") die Zeile rausfinden. Selbst dann, wenn der User neben den Button geklickt hat - der Button ist vor allem für Tastaturbedienung und Assistenztechnik wichtig.
Bei mehr als einem Button musst Du erstmal mit event.target.closest("button") zum Button, um abzufragen, welcher Button es ist, und kannst dann von da aus weiter zur Row, um den Datenkontext zu finden.
Rolf
Hallo Rolf,
Bei mehr als einem Button musst Du erstmal mit event.target.closest("button") zum Button, um abzufragen, welcher Button es ist, und kannst dann von da aus weiter zur Row, um den Datenkontext zu finden.
kennst du denn eine "einfache" Möglichkeit, herauszubekommen, in welche Spalte (oder Zeile) der geklickte Button liegt? Beim Tabellensortierer bekommt jeder Button seinen eigenen Handler mit Spaltennummer.
Gruß
Jürgen
Hallo Rolf,
D.h. bei Dir das thead-Element der tabelle. Die Table als solche ginge auch, weil Du im Table Header keine Buttons hast.
Meintest du eventuell tbody? Der thead ist hier ja in der Tat uninteressant.
Aber, wie gesagt, du hast nur eine Tabelle, deswegen ist diese Überlegung unnötig. Leg den click-Handler auf die Tabelle.
Das widerspricht allerdings dem, was Felix hier schrub:
<tbody>
-Elemente sind keine interaktiven Elemente. Du solltest sie auch nicht mit einem EventListener interaktiv machen.
Oder gibts dahingehend einen Unterschied zwischen table und tbody?
Schöne Grüße
Nico
Lieber Nico,
Das widerspricht allerdings dem, was Felix hier schrub:
<tbody>
-Elemente sind keine interaktiven Elemente. Du solltest sie auch nicht mit einem EventListener interaktiv machen.
jein. 😉
Nicht OK: Klick auf eine Tabellenzelle ohne interaktives Element löst eine Interaktion aus.
OK: Klick auf ein <button>
-Element innerhalb einer Tabellenzelle löst eine Interaktion aus.
Dabei ist es völlig egal, auf welches (Vorfahren-)Element der zugehörige Listener gesetzt wurde. In Deinem Fall ist es sinnvoll, diesen einen Listener an das <table>
-Element zu hängen, damit Klicks auf dessen Nachfahrenelemente (bevorzugter Weise Buttons) entsprechend ausgewertet werden.
Es gab zu dieser Thematik einen sehr ausführlichen Thread in diesem Forum, als ich damals mein Tutorial zu Tic-Tac-Toe vorgestellt hatte.
Ein Grenzfall wäre, dass der User neben den Button klickt, aber noch innerhalb der Tabellenzelle getroffen hat. Will man dem User nun vorgaukeln, dass er schon noch den Button erwischt hätte, kann man im Listener nun prüfen, ob es in dieser Tabellenzelle einen Button gibt, und wenn ja, so tun, als hätte dieser das Event ausgelöst.
Liebe Grüße
Felix Riesterer
@@Felix Riesterer
Ein Grenzfall wäre, dass der User neben den Button klickt, aber noch innerhalb der Tabellenzelle getroffen hat. Will man dem User nun vorgaukeln, dass er schon noch den Button erwischt hätte, kann man im Listener nun prüfen, ob es in dieser Tabellenzelle einen Button gibt, und wenn ja, so tun, als hätte dieser das Event ausgelöst.
Das würde ich nicht machen. Eher mit CSS dafür sorgen, dass der Button die gesamte Tabellenzelle ausfüllt.
Jolan tru
Das würde ich nicht machen. Eher mit CSS dafür sorgen, dass der Button die gesamte Tabellenzelle ausfüllt.
Das habe ich jetzt mal noch gemacht: https://fsv-optik.de/tests/dialog_fetch_3.html
Dort ist jetzt auch die Funktion (wieder) eingebaut, dass Änderungen (farblich markiert) in die Tabelle eingetragen werden. Was ich nicht hingekriegt habe, ist, den Button die td auch in der Höhe ausfüllen zu lassen.
Schöne Grüße
Nico
Hallo Nico R.,
Was ich nicht hingekriegt habe, ist, den Button die td auch in der Höhe ausfüllen zu lassen.
Wie hast Du es versucht? height:100% am Button? Diese Angabe bezieht sich auf das Elternelement. Das ist ein td. Welche Höhe hat das td? Keine explizite, du überlässt das Finden der Höhe dem Tabellenlayout.
Um mögliche Endlos-Abhängigkeiten zu vermeiden, ist CSS ziemlich schnell fertig damit, nach Bezugspunkten für Prozentangaben zu suchen. Der Button hat 100%, das Elternelement hat keine definite Höhe, und Schluss. Der Button bleibt bei seiner "natürlichen" Höhe, die sich aus Inhalt und Padding zusammensetzt. Und wird dann vom Table-Layout vertikal in der Zelle zentriert.
Lösung von außen nach innen: Man könnte den tds eine fixe Höhe von 3em oder so geben. Dann würde height:100% am Button funktionieren. Das Padding am Button und der ID-Zelle sollte man dann weglassen.
Lösung von innen nach au0en: Dass die Buttons nicht hoch genug sind, liegt an der ID-Spalte. Da ist padding:1em gesetzt. Ja, für die Buttons auch, ABER Buttons haben per Default einen Font, der etwas kleiner ist als der Normalfont. Weshalb 1em dort weniger ist als in der ID-Spalte. Gib den Buttons font: inherit. Dann sind Texte UND Padding gleich groß und die Buttons so hoch wie die Zelle. Sobald Du allerdings zweizeilige Zellinhalte hast, geht das wieder schief. Good Lack, wie die Maler sagen!
Beides ist machbar. Bei einer riesigen Tabelle ist die Lösung "von außen nach innen" vermutlich performanter, weil der Browser nicht lange überlegen muss, wie hoch die Zellen sind. Ansonsten ist aus meiner Sicht beides gleich gut.
Rolf
Hallo Rolf,
ich hab mich für Lösung zwei entschieden. Jetzt passts.
ABER Buttons haben per Default einen Font, der etwas kleiner ist als der Normalfont. Weshalb 1em dort weniger ist als in der ID-Spalte.
Das ist doch voll gemein. Die armen buttons...
Mir kams schon so vor, als wäre die ID-Zahl etwas größer, ich dachte aber, das wäre ne optische Täuschung. Wieder was gelernt. Ich glaube dieser Streich wurde mir schon ein ums andere Mal gespielt, ohne dass ich dahinter gekommen wäre.
Schöne Grüße
Nico
Hallo Felix,
Nicht OK: Klick auf eine Tabellenzelle ohne interaktives Element löst eine Interaktion aus.
OK: Klick auf ein
<button>
-Element innerhalb einer Tabellenzelle löst eine Interaktion aus.Dabei ist es völlig egal, auf welches (Vorfahren-)Element der zugehörige Listener gesetzt wurde...
Aaah, okay. Jetzt hab ichs kapiert 👍
Schöne Grüße
Nico
@@Rolf B
Wo Rolf das schon so schön erklärt, hat, hab ich mal ein Beispiel gebaut, wo es mehrere gleichartige Komponenten (Tabellen) gibt.
Dort gibt es jeweils in den Tabellenzeilen Buttons, die verschiedene Aktionen auf der jeweilen Zeile auslösen.
Disclaimer: Die Lösch-Funktion bräuchte noch eine Abfrage „Wollen Sie wirklich?“ und/oder eine Undo-Funktion.
Jolan tru
@@Nico R.
Der Link zu deinem Beispiel funktioniert nicht. Ich musste die Adresse von Hand reinkopieren. Hier nochmal für andere Mitleser: https://codepen.io/gunnarbittersmann/pen/bNGywOV?editors=1011
Huch, wie konnte denn das passieren? Und als ich’s berichtigen wollte, war mir schon jemand zuvorgekommen.
An welcher Stelle würde sich denn in meiner Tabelle das Komponentenelement befinden?
Nicht in, sondern die Tabelle ist die Komponente. Auch nicht tbody
, da ein solcher für sich keine vollständige Komponente ist.
Jolan tru
Hallo Nico,
Aber warum registstrierst du die überhaupt im Eventhandler? Wenn du die da rausnimmst, sollte sich das Problem erledigt haben.
Mit anderen Worten: registrier sie einfach da, wo Du let erstaufruf=true stehen hast. Ohne once-Option. Das Dialog-Element bleibt auch nach dem Close vorhanden, die Eventhandler auch.
Deregistrieren gänge rein theoriesam natürlich auch, ist aber (a) viel zu umständlich und geht (b) nicht so einfach mit anonymen Funktionen. Bei der Deregistrierung muss EXAKT das gleiche Funktionsobjekt verwendet werden wie bei der registrierung, und wenn die Funktionen im click-Eventhandler definiert werden, hast Du bei jedem click ein anderes Funktionsobjekt. D.h. du müsstest die Handlerfunktionen in lokalen Variablen des click-Handlers speichern, dann bleiben sie bis zum Schließen vorhanden (-> Closure)
Rolf
Lieber Nico,
button.addEventListener("click", function(event){ //... dialog_ok.addEventListener("click", function(event) { // ... }, {once: true} ); dialog.addEventListener("keydown", function(event) { // ... }, {once: true} ); });
Du willst also, dass nur dann, wenn ein spezieller Button mittels einem click
-Event bedient wird, dass zwei andere Bedienelemente einen Eventlistener zugewiesen bekommen, egal ob sie schon einen haben oder nicht. Das bedeutet, dass mit jedem Klick auf diesen einen Button immer noch mehr an Eventlistenern hinzugefügt wird.
Oder missverstehe ich da etwas?
Liebe Grüße
Felix Riesterer
Hallo Felix,
ja, das ist leider so. Was ich aber mit removeEventListener() heilen konnte. Elegant und effezient ists nicht, aber es handelt sich bei dem Script um eine Funktion für das Redaktionssystem der Seite, die nicht oft genutzt wird. Für einen Umbau der verschiedenen Schachtelungen fehlte mir einfach die Lust. Manchmal ists gut, wenns einfach läuft, find ich 😀
Schöne Grüße
Nico
Lieber Nico,
Manchmal ists gut, wenns einfach läuft, find ich 😀
also ich finde es gut, wenn es immer „einfach läuft“. 😉
Was ich aber mit removeEventListener() heilen konnte.
Soso, „heilen konnte“? Aus meiner Sicht hattest Du da etwas gebaut, das repariert gehört, weil seine Struktur nicht wirklich sinnvoll ist. Deine „Heilung“ ist in Wirklichkeit ein sogenannter Hack, oder schöner formuliert: ein „Hotfix“.
Zum Verständnis: Du willst, dass gewisse Buttons mittels Eventlistener-Funktionen gewisse Funktionalitäten erhalten. Soweit OK. Dass Du diese aber jedes Mal auf Knopfdruck erneut zuweist, anstatt beim Laden der Seite nur einmal, sodass Du sie auch jedes Mal wieder entfernen musst, anstatt sie lassen zu können, zeigt, dass Deine Vorgehensweise verbesserbar ist. Hier ein Beispiel:
document.addEventListener('DOMContentLoaded', function () {
const
button = document.getElementById("button"),
dialog = document.getElementById("dialog"),
dialog_ok = document.getElementById("dialog_ok"),
dialog_abbr = document.getElementById("dialog_abbr");
// alle Zutaten vorhanden?
if (button && dialog && dialog_ok && dialog_abbr) {
button.addEventListener("click", e => {
// nur wenn noch nicht geöffnet
if (!dialog.open) {
dialog.showModal();
}
});
dialog_abbr.addEventListener("click", e => {
console.log("Abbrechen-Button");
dialog.close();
});
dialog_ok.addEventListener("click", e => {
console.log("OK-Button");
dialog.close();
});
document.addEventListener("keydown", e => {
if (dialog.open && "Enter" == e.key) {
console.log("Enter-Taste");
dialog.close();
}
});
}
});
Das Beispiel zeigt, wie man die Eventlistener-Funktionen nur einmal dauerhaft im Dokument registriert. Das ist verlässlicher, als das nur beim Klicken eines Buttons zu tun, denn solange das Dialogfenster nicht geöffnet ist, stört es ja nicht, wenn die Eventlistener da sind, da sie im obigen Beispiel ja prüfen, ob sie gerade überhaupt gebraucht werden.
Liebe Grüße
Felix Riesterer
Hallo Felix,
ich hatte auch schon einen längeren Beitrag angedacht, der das diskutiert, und auch schon ein Fiddle angefangen. Aber Nico hat uns nur ein Beispiel für den Problemtyp gezeigt und nicht das ganze Umfeld. Da gibt's noch einiges Gefummel an Daten, wie ich verstanden habe, und ohne das zu kennen, können wir kaum erklären, wie er das korrekt machen muss.
Das Hauptproblem ist wohl, dass er bei einmaliger Registrierung mit den Daten nicht zurecht kommt, was auf einen Besuch in closure hell hindeutet. Eine abstrakte Hilfestellung ohne Kenntnis der Seite (die Nico uns nicht geben kann/will, weil es private Interna sind und er scheinbar keine öffentlich erreichbare Testinstallation hat) war mir zu komplex.
Wenn man das Thema schon angeht, dann auch gleich so, dass man ein <form method="dialog"> in den Dialog packt. In dem Fall muss man sich nur auf das submit-Event des Forms registrieren und auf das cancel-Event des Dialogs. Das eigentliche Button-Handling entfällt dann.
Im submit-Event behandelt man ok- und cancel-Button. Ich habe nicht rausgefunden, wie man einen Button konfigurieren muss, der den Dialog automatisch abbricht. Im cancel-Event landet man nach meiner Erfahrung nur, wenn der User die Esc-Taste drückt (ggf. auch bei Klick außerhalb des Dialogs).
Ich habe aber keine Ahnung, ob Nico so tief überhaupt einsteigen will, und ins Leere predigen mag ich auch nicht. Deswegen habe ich es gelassen. Auch wenn's mich gejuckt hat, das Thema ist durchaus von allgemeinem Interesse. Ich werde wohl irgendwas was dazu ins Wiki schreiben.
Rolf