Linuchs: Klick auf Link soll nicht auf Elter (parent) wirken

Moin,

wir hatten ja diskutiert, wie man einem Objekt auf der HTML-Seite per Eventlistener "Klick" eine Javascript-Funktion zuordnen kann.

Dabei wird der "Klick" nicht an das Elter weitergereicht.

Bei normalen Links per <a href= ...> wird der Klick aber durchgereicht. Das ist ein Problem, denn die Zeile in einer Liste wird farbig markiert, wenn man auf eine leere Stelle der Zeile klickt. Dann kann diese Zeile (Adresse, Event, ...) bearbeitet werden und bei Rückkehr sieht man, wo man zuletzt war. Einige Programme zählen auch die angeklickten Zeilen.

Ein Klick auf einen Link in der Zeile löscht nun aber die Markierung. Das ist nicht erwünscht. Wie kann ich das verhindern?

Öffentliches Beispiel: http://remso.eu/?TYP=7

Linuchs

  1. Hallo Linuchs,

    event.stopPropagation() ist dein Freund. Wikiartikel

    Bis demnächst
    Matthias

    --
    Dieses Forum nutzt Markdown. Im Wiki erhalten Sie Hilfe bei der Formatierung Ihrer Beiträge.
    1. Hallo Mathias,

      event.stopPropagation() ist dein Freund. Wikiartikel

      Das klappt prima. Da setze ich dann auch gleich das Klick-Event für die Färbung des Hintergrunds:

      // wenn DOM geladen ...
      window.addEventListener('DOMContentLoaded', function ( ) {
        // Positionen per Klick einfaerben
        obj_position = document.getElementsByClassName( "position" );
        for ( i=0; i<obj_position.length; i++ ) {
          obj_position[i].addEventListener('click', function () { 
            switchMarkierung(this);
          });
        }
        // Klick auf <a ... nicht wieterreichen
        obj_href = document.getElementsByTagName( "a" );
        for ( i=0; i<obj_href.length; i++ ) {
          obj_href[i].addEventListener('click', function (event) {
            event.stopPropagation();
          });
        }
      });
      

      Aber warum kann ich die Funktion switchMarkierung nicht direkt einhängen? Dies geht nicht, offenbar wird this nicht oder nicht richtig an die Funktion übergeben:

          obj_position[i].addEventListener('click', switchMarkierung(this) );
      

      Fehler: TypeError: zeile.className is undefined in standard.js:21:0

      function switchMarkierung( zeile )
      {
        // Toggle Hintergrund
        if ( zeile.className.indexOf( "bgmarkiert" ) == (-1) ) {
      [21]    zeile.className = zeile.className +" bgmarkiert";  // Markierung setzen
          markierte_positionen++;
        } else {
          zeile.className = zeile.className.replace( " bgmarkiert", "" ).replace( "bgmarkiert", "" );  // Markierung entfernen
          markierte_positionen--;
        }
      

      Linuchs

      1. Lieber Linuchs,

        zeile.className = zeile.className +" bgmarkiert";  // Markierung setzen
        

        meine Empfehlung: Nutze zeile.classList.add("bgmarkiert"); (Wiki, CanIUse.com)

        Liebe Grüße,

        Felix Riesterer.

      2. Lieber Linuchs,

        Aber warum kann ich die Funktion switchMarkierung nicht direkt einhängen? Dies geht nicht, offenbar wird this nicht oder nicht richtig an die Funktion übergeben:

            obj_position[i].addEventListener('click', switchMarkierung(this) );
        

        Du kannst nicht das Schlüsselwort this als Variablenbezeichner verwenden. Nimm einen anderen:

        obj_position[i].addEventListener('click', switchMarkierung(ev));
        

        Das Event-Objekt in der Variable ev kennt verschiedene Eigenschaften, darunter auch target (CanIUse.com).

        Liebe Grüße,

        Felix Riesterer.

        1. Hi,

              obj_position[i].addEventListener('click', switchMarkierung(this) );
          

          Du kannst nicht das Schlüsselwort this als Variablenbezeichner verwenden.

          Tut er das denn?

          Nach meinem Verständnis wird hier switchMarkierung mit dem Parameter this aufgerufen und der Returnwert dieser Funktion als Eventhandler an das click-Event gehängt.

          m.E. müßte es hier lauten

             obj_position[i].addEventListener('click', switchMarkierung);
          

          Vorausgesetzt, es ist irgendwo eine Funktion switchMarkierung definiert.

          Nimm einen anderen:

          obj_position[i].addEventListener('click', switchMarkierung(ev));
          

          Die Verwendung eines nicht-definierten Wertes als Parameter beim Aufruf macht die Sache nicht wirklich besser.

          cu,
          Andreas a/k/a MudGuard

        2. Hallo Felix

          Du kannst nicht das Schlüsselwort this als Variablenbezeichner verwenden. Nimm einen anderen:

          obj_position[i].addEventListener('click', switchMarkierung(ev));
          

          Das Event-Objekt in der Variable ev kennt verschiedene Eigenschaften …

          Ich würde mal vermuten, dass der von dir empfohlene Code einen Reference Error produziert, da keine Bindung für den Bezeichner ev erzeugt wurde. Was darüber hinaus auch eine gute Sache wäre, weil es kaum im Sinne des Verwenders ist, die Funktion, die als Handler registriert werden soll, unmittelbar aufzurufen und der Methode addEventListener deren Rückgabewert als Argument zu übergeben. Zumal es sich bei diesem aller Wahrscheinlichkeit nach um den Wert undefined handeln wird.

          Hier hast du dich wohl von Linuchs verwirren lassen! – Da er diesen Fehler aber nicht zum ersten Mal begangen hat, sondern dasselbe bereits in diesem Beitrag versucht hat, erscheint es mir doch geboten, an ihn gerichtet nochmal etwas genauer auf den Unterschied zwischen der Definition einer Funktion und deren Aufruf einzugehen, auch wenn er mit der entsprechenden Syntax eigentlich vertraut sein sollte.

          Ahh, jetzt ja. Die Klammern sind zu viel. Hatte ich übernommen von

          window.addEventListener('DOMContentLoaded', function() {...})
          

          gehören die da auch weg?

          Linuchs


          Fangen wir also an mit der Definition von Funktionen, wobei wir uns an dieser Stelle aber nur mit gewöhnlichen Funktionen beschäftigen werden und wir Sonderfälle wie beispielsweise Pfeilfunktionen oder Klassen mal außen vor lassen. Wenn es nun also um Funktionsdefinitionen geht, dann stellen wir zunächst fest, dass es zwei syntaktische Varianten gibt, mit denen eine Funktion definiert werden kann, die beide mit dem Schlüsselwort function eingeleitet werden.

          // named function definition
          
          function BindingIdentifier (/* FormalParameters */) {/* FunctionBody */}
          

          Bei einer benannten Funktion folgt auf das Schlüsselwort function der Bezeichner, der an das auf diese Weise erzeugte Funktionsobjekt gebunden wird. Wenn eine Funktion deklariert wird, dann muss sie als benannte Funktion definiert werden, da sie sonst nicht referenziert werden kann. Wird eine Funktion hingegen über einen Ausdruck definiert, dann kann der Bezeichner weggelassen werden.

          // anonymous function definition
          
          function (/* FormalParameters */) {/* FunctionBody */}
          

          Abhängig davon ob die Funktion benannt oder anonym ist, folgt entweder auf den Funktionsbezeichner oder direkt auf das Schlüsselwort function die Liste der Parameter der Funktion. Die Parameter werden dabei innerhalb von runden Klammern notiert, wobei festzuhalten ist, dass unabhängig davon, ob für die Funktion Parameter deklariert werden oder nicht, die runden Klammern zwingend notiert werden müssen, weil sonst in jedem Fall ein Syntax Error geworfen wird.

          Nach den runden Klammern für die Parameter der Funktion folgen dann die geschweiften Klammern, die den Körper der Funktion umschließen, also die Anweisungen, die beim Aufruf der Funktion ausgeführt werden sollen. Zwar gibt es Ausführungsumgebungen wie zum Beispiel den Firefox Browser, die es erlauben, die geschweiften Klammern wegzulassen, aber in der Regel wird hierdurch ein Syntaxfehler erzeugt, weshalb auch sie zwingend notiert werden sollten.

          // call expression
          
          IdentifierReference(/* ArgumentList */);
          

          Wenn es nun um den Aufruf einer zuvor definierten Funktion geht, dann ist anzumerken, dass es eine Unzahl verschiedener syntaktischer Varianten hierfür gibt, die sich jedoch zumeist nur darin unterscheiden, wie die aufzurufende Funktion referenziert wird. In der einfachsten Variante, deren Syntax im Beispiel oben dargestellt ist, wird die Funktion die aufgerufen werden soll einfach über den Bezeichner referenziert, an den sie zuvor bei der Definition gebunden wurde.

          Soll eine Funktion hingegen als Methode aufgerufen werden, dann wäre zunächst über einen passenden Elementausdruck (Member Expression) das Objekt zu referenzieren, über das die Methode angesprochen werden soll. Das könnte wie im nächsten Syntaxbeispiel aussehen, in dem zunächst das Objekt über einen Bezeichner referenziert wird und die Methode dann über die Punktnotation und ihren Namen.

          // call expression
          
          IdentifierReference.IdentifierName(/* ArgumentList */);
          

          Nachdem die Funktion also hoffentlich erfolgreich referenziert wurde, muss dem Interpreter natürlich noch mitgeteilt werden, dass die Funktion auch aufgerufen werden soll. Hierzu werden nun wiederum runde Klammern notiert, innerhalb derer bei Bedarf die Argumente eingefügt werden können, welche der Funktion übergeben werden sollen und mit deren Werten die gegebenenfalls bei der Funktionsdefinition deklarierten Parameter initialisiert werden.


          Die runden Klammern besitzen also in Bezug auf die Syntax von Funktionen zwei völlig unterschiedliche Bedeutungen. Bei der Definition einer Funktion umschließen sie die formalen Parameter beziehungsweise die leere Parameterliste. Werden sie hingegen nach dem Bezeichner einer bereits zuvor definierten Funktion notiert, dann sorgen sie dafür dass diese Funktion aufgerufen wird und sie ermöglichen es, dabei Argumente an die Funktion zu übergeben.


          Nun gibt es, wie weiter oben bereits erwähnt, von den beiden syntaktischen Varianten abgesehen zwei Arten, wie Funktionen definiert werden können, nämlich entwerder über eine Deklaration oder aber über einen Ausdruck. Ob es sich um eine Funktionsdeklaration oder um einen Funktionsausdruck handelt hängt dabei davon ab, in welchem Kontext die Funktion definiert wird.

          // function declaration
          
          function identifier (parameter) {
            // do something
          }
          

          Wird eine Funktion solo im globalen Gültigkeitsbereich oder im Körper einer anderen Funktion notiert, so wie in dem Beispiel oben, dann handelt es sich um eine Deklaration. In diesem Fall muss zwingend eine benannte Funktion definiert werden. Das heißt, hier muss zwischen dem Schlüsselwort function und den runden Klammern für die Parameter ein valider Bezeichner notiert werden, denn sonst wäre es nicht möglich, die Funktion später zu referenzieren.

          // variable statement
          
          var value = identifier;
          

          Weil wir unserer deklarierten Beispielfunktion einen Bezeichner gegeben haben, können wir diese also innerhalb des Gültigkeitsbereichs dieses Bezeichners ansprechen. So könnten wir die Funktion wie in dem Beispiel oben einer Variable als Wert zuweisen, indem wir als Zuweisungsausdruck einfach den bei der Deklaration angegebenen Bezeichner notieren. Wir referenzieren also die Funktion.

          // named function expression
          
          var value = function identifier (parameter) {
            // do something
          };
          

          Statt bei der Zuweisung an die Variable eine Referenz auf eine zuvor deklarierte Funktion anzugeben, können wir die Funktion jedoch auch direkt innerhalb des Zuweisungsausdrucks definieren. Bezogen auf das Beispiel oben handelt es sich also nicht mehr um eine Deklaration, sondern um einen Ausdruck.

          Da es sich in ECMAScript bei Funktionen im Prinzip lediglich um Werte handelt, ist es möglich, diese überall dort zu definieren, wo ein Ausdruck notiert werden darf. Sprich, ein Funktionsausdruck ist einfach ein Ausdruck, welcher zu einem Wert aufgelöst wird, bei dem es sich um ein Funktionsobjekt handelt. Dieser Wert kann dann etwa wie im letzten Beispiel einer Variable zugewiesen werden.

          var value = function identifier ( ) {
            // do something
          };
          
          identifier( ); // Reference Error
          

          Nun gibt es allerdings ein paar Unterschiede zwischen Funktionen die deklariert wurden und Funktionen, die über einen Ausdruck definiert wurden. So wird bei einer Funktionsdeklaration der Bezeichner der Funktion an die jeweilige lexikalische Umgebung gebunden, das heißt, die Funktion kann überall innerhalb dieser Umgebung über ihren eigenen Namen referenziert werden. Das ist bei Funktionen die über einen Ausdruck definiert wurden anders, wie das Beispiel oben zeigt, bei dem der Versuch des Funktionsaufrufs über den Bezeichner der Funktion einen Reference Error produziert.

          value( ); // works!
          

          Wird eine Funktion über einen Ausdruck definiert, dann kann ihr Bezeichner nur innerhalb der Funktion selbst referenziert werden, nicht jedoch im umgebenden Gültigkeitsbereich. Wenn die Funktion, welche der Variable mit dem Bezeichner value als Wert zugewiesen wurde also referenziert werden soll, zum Beispiel um sie aufzurufen, dann kann dies nur über den Bezeichner value geschenen.

          // anonymous function expression
          
          window.addEventListener('DOMContentLoaded', function ( ) { });
          

          Womit wir dann zurück beim Ausgangsbeispiel wären. Denn da bei einer als Ausdruck definierten Funktion ohnehin nicht die Möglichkeit besteht, sie von außerhalb über ihren eigenen Bezeichner anzusprechen, ist es erlaubt ihn einfach wegzulassen, also eine anonyme Funktion zu definieren.

          Weil darüber hinaus die Übergabe eines Arguments beim Aufruf einer Funktion oder einer Methode, wie etwa der Methode addEventListener, nichts anderes ist als die Notierung eines Ausdrucks, kann eine solche anonyme Funktion auch an dieser Stelle definiert werden, also direkt bei der Übergabe des Arguments, was genau das ist, was in dem Beispiel oben passiert.

          window.addEventListener('DOMContentLoaded', /* anonymous function definition */);
          

          Es wird also die Methode addEventListener aufgerufen, indem sie zunächst über einen Elementausdruck referenziert wird, hinter dem dann die runden Klammern für den Aufruf und die Übergabe der Argumente notiert werden. Dabei wird nun als erstes Argument der String mit dem Ereignistyp notiert, und da die Methode zudem als zweites Argument ein Funktionsobjekt erwartet, wird ein solches in Form eines anonymen Funktionsausdrucks definiert.

          // function declaration
          
          function handler (event) {
            console.info(event.target.tagName);
          }
          
          // call addEventListener with reference
          
          document.body.addEventListener('click', handler);
          

          Aber es ist natürlich im Prinzip vollkommen egal, wie das Funktionsobjekt beim Aufruf der Methode addEventListerner übergeben wird. So wie weiter oben einer Variable eine zuvor deklarierte Funktion als Wert zugewiesen wurde, indem diese über ihren Bezeichner referenziert wurde, kann natürlich auch bei der Argumentübergabe eine Referenz auf eine zuvor definierte Funktion übergeben werden.

          Dabei ist aber darauf zu achten, dass eine Referenz auf die Funktion übergeben werden soll und nicht der Rückgabewert der Funktion, der standardmäßig undefined ist. Es ist also lediglich der Bezeichner der Funktion in der Liste der Argumente zu notieren, der dann zu dem hoffentlich richtigen Funktionsobjekt aufgelöst wird. ;-)

          // call addEventListener with return value
          
          document.body.addEventListener('click', handler( ));
          

          Werden hingegen runde Klammern nach dem Bezeichner notiert, dann wird die Funktion entsprechend direkt aufrufen, sodass der Wert des Ausdrucks, des Arguments und damit des an die Methode addEventListener übergebenenen Wertes schließlich der Rückgabewert der Funktion ist und nicht die Funktion selbst.


          Zusätzliche Lektüre: Funktionen im Wiki


          Beste Grüße,

          Orlok

      3. Hallo Linuchs,

        // wenn DOM geladen ...
        window.addEventListener('DOMContentLoaded', function ( ) {
          // Positionen per Klick einfaerben
          obj_position = document.getElementsByClassName( "position" );
          for ( i=0; i<obj_position.length; i++ ) {
            obj_position[i].addEventListener('click', function () { 
              switchMarkierung(this);
            });
          }
          // Klick auf <a ... nicht wieterreichen
          obj_href = document.getElementsByTagName( "a" );
          for ( i=0; i<obj_href.length; i++ ) {
            obj_href[i].addEventListener('click', function (event) {
              event.stopPropagation();
            });
          }
        });
        

        Prüfe lieber, ob es sich bei dem target um ein a handelt:

        positions = document.getElementsByClassName( "position" );
        for(i = 0; i < obj_position.length; i++) {
          positions[i].addEventListener('click', function(ev) {
            if(ev.target.nodeName == 'A') {
              return;
            }
        
            switchMarkierung(this);
          });
        }
        

        LG,
        CK