ebody: Javascript - getter Eigenschaft ist undefined

problematische Seite

Hallo,

eine Klasse Movies erfasst das anklicken von Checkboxen und fügt den Inhalt in ein Objekt, welches als Wert in set filter({}) gespeichert wird.

Ein Klick auf einen Button gibt den Wert von get filter() aus. Hier ist Movies.filter aber undefined.

Innerhalb der Methode Movies.saveSelection() gebe ich Movies.filter ebenfalls aus und hier enthält Movies.filter auch den richtigen Wert.

Warum nicht innerhalb der Funktion menuShow()?

Gruß ebody

  1. problematische Seite

    Hallo,

    this zeigt in Eventhandlern nicht auf die Klasse, sondern auf das eventauslösende Element. Wir hatten das erst vor kurzem:

    https://forum.selfhtml.org/self/2021/nov/24/manoverkritik-oop-in-js/1794077#m1794077

    Gruß
    Jürgen

    1. problematische Seite

      Hallo Jürgen,

      vielen Dank für den Hinweis.

      Ich habe jetzt vor dem Eventhandler this in einer Variablen that gespeichert und kann so auf Methoden innerhalb des Eventhandlers und der Klasse zugreifen. Quelle.

      let that = this;
      element.addEventListener('change', function (evt) {
      
       that.filter = this.value;
      
      });
      

      Gruß ebody

      1. problematische Seite

        Hallo ebody,

        nein, ein that ist eine Krücke. Eine Krücke mit Tradition, aber trotzdem eine Krücke.

        In einem function-Eventhandler ist this das currentTarget des Events. D.h. das Element, auf dem addEventListener aufgerufen wurde. Das kann mit target übereinstimmen, muss aber nicht, wenn bubbling stattfindet.

        Es ist demnach schwer verständlich, im change-Eventhandler this zu verwenden und damit das DOM Element zu meinen, in dem der change stattfand. Es ist noch schwerer verständlich, wenn man this und event.target gleichzeitig verwendet. Dein 4 Monate älteres Ich fragt sich dann um Ostern rum: Was hat der verfluchte Kerl da kurz vor Weihnachten nur getan? Warum mal this, warum mal event.target - was war die Notwendigkeit dafür?!?!?! Gibt keine. Verwende immer event.target.

        Bei Dir kommt verschärfend hinzu, dass der Eventhandler eine lokale Funktion in einer Methode ist. In einem solchen Umfeld verbinde ich this eigentlich automatisch mit dem Objekt, zu dem die Methode gehört. Darf ich in dem Moment aber nicht. Ich muss ständig nachdenken, wofür jetzt this steht. Und das ist Mist. Jedes Nachdenken über die richtige Bedeutung ist einmal zu viel nachgedacht.

        Weiteres Problem: Du registrierst die change-Handler in einer Schleife auf jeder Checkbox einzeln. Lass das. Registriere den Handler nur einmal, auf #chipsbox. Die change Events entstehen auf den Checkboxen und blubber von da aus im DOM nach oben. Wenn sie am chipsbox-div vorbeikommen, kannst Du sie mit einer einzigen Eventhandler Registrierung behandeln und kannst Dir die Registrierschleife sparen.

        Um das this-Problem zu lösen, kannst Du - aus Lesbarkeitsgründen - die Handlerfunktion separat aufschreiben und als Eventhandler die an this gebundene Funktion übergeben. Oder du kannst sie gleich als Pfeilfunktion notieren, dann entsteht keine neue this-Bindung und du verwendest automatisch das this des Eltern-Scopes.

        saveSelection() {
           let objFilterCollection = {};
           const chipsBox = document.getElementById('chipsBox');
           chipsBox.addEventListener(
                       'change', event => {
              // Handle on
           });
        }
        

        Nächster Punkt: An Arrays herumzufummeln, um einzeln Werte reinzuschieben oder rauszuholen, ist lästig, mühsam, fehlerträchtig. Kurz: sollte man lassen. Viel einfacher ist es, das Array bei jeder Änderung neu aufzubauen. Dabei kann man sich sogar noch von einem CSS Selektor helfen lassen - die aktiven Checkboxen kriegst Du mit querySelectorAll("input[type=checkbox]:checked").

        Wenn Du dann noch alle Checkboxen zu einem Thema in ein Fieldset kapselst, kannst Du den Key ans Fieldset stellen und brauchst ihn nicht an jede Checkbox zu schreiben. Dorthin kommen nur die Values. Und die auch nicht als data, sondern als value-Attribut. Dafür gibt's das schließlich. WENN Du unbedingt den Key an der Checkbox haben willst - nagut, steck ihn in's name-Attribut. Wenn Du das in einem Form hast und an einen PHP Server submitten willst, dann mit name="Genre[]" oder so, damit das Checkboxarray am Server auch als Array ankommt. Aber solange Du das nur in JavaScript machst, ohne Server, brauchst Du Dich um ein name Attribut der Checkboxen nicht zu kümmern.

        Meine Idee zum Thema

        Der komplexeste Teil in meinem Fiddle sind vermutlich diese beiden Zeilen:

        const keySet = container.querySelectorAll(`input[type=checkbox]:checked`);
        this.filters[key] = Array.prototype.map.call(keySet, checkbox => checkbox.value);
        

        Aber dafür übernehmen die auch den größten Teil deines Scripts. Zeile 1: container ist eine Variable, die auf das fieldset zeigt. Mit querySelectorAll suche ich im fieldset alle checkboxen, die checked sind. Das Ergebnis ist eine NodeList.

        Eine NodeList ist kein Array, deshalb hat sie keine map-Methode. Aber sie ist array-artig genug, um sie der map-Methode von Array.prototype als Array unterzuschieben (dafür braucht's nur eine length-Eigenschaft und Werte, die mit 0,1,2,3... indexiert sind).

        Array.prototype.map.call

        tut genau das. call ist eine Methode, die jedes Function-Objekt hat. Man kann sie damit aufrufen und einen Wert für this festlegen. Ich verwende damit also die map-Methode der Arrays für die NodeList, die in keySet steht. Die map-Methode bekommt einen Callback, der auf jedes Array-Element angewendet wird, und das Ergebnis ist ein Array mit den Ergebnissen. D.h. ich bekomme ein Array mit den value-Werten aller Checkboxen, die angehakt sind. Und das speichere ich in this.filters[key]. Wenn da vorher schon was stand, egal, weg damit, ist veraltet.

        So kurz und knackig kann das gehen 😉

        Rolf

        --
        sumpsi - posui - obstruxi
        1. problematische Seite

          Tach!

          Eine NodeList ist kein Array, deshalb hat sie keine map-Methode. Aber sie ist array-artig genug, um sie der map-Methode von Array.prototype als Array unterzuschieben (dafür braucht's nur eine length-Eigenschaft und Werte, die mit 0,1,2,3... indexiert sind).

          Array.prototype.map.call

          tut genau das.

          Einfacher und mehr gerade heraus ist es, ein echtes Array draus zu machen. Das schreibt sich auch kürzer.

          this.filters[key] = [...keySet].map(checkbox => checkbox.value);
          

          dedlfix.

          1. problematische Seite

            Hallo dedlfix,

            gute Idee. An den ... Operator zum umkopieren eines arrayoiden Dings (oder gar iterierbaren Dings? Müsste ich jetzt recherchieren) in ein Array hab ich mich noch icht gewöhnt.

            (Zurück aus dem Wiki) - ja, iterierbar reicht.

            Rolf

            --
            sumpsi - posui - obstruxi
        2. problematische Seite

          Hallo Rolf,

          vielen Dank für die vielen guten Hinweise und Tipps!

          Gruß ebody

  2. problematische Seite

    Tach!

    Deine Frage wurde schon beantwortet. Hier kommen weitere Anmerkungen.

     set filter(objFilter={}) {
       this.objFilter = objFilter;
     }
    

    Ein Setter kann keine optionalen Parameter haben. Das ergibt auch keinen Sinn. Der Setter wird per Zuweisung aufgerufen und niemals ohne eine solche. Also ist ein Wert für den Parameter zwingend vorhanden. Wenn das eine Initialisierung von this.objFilter sein sollte, so kann das im Konstruktor erfolgen.

      let objFilterCollection = {};
    

    Das ist die einzige Zuweisung an diese Variable. Sie kann deshalb als const deklariert werden. Die Referenz auf das Objekt bleibt konstant, die Änderungen an seinem Inhalt ändern daran nichts.

    Für alle anderen Stellen, an denen du let verwendest, ergibt sich ebenfalls keine Notwendigkeit. Alle Variableninhalte bleiben unverändert.

    dedlfix.