A. Henkelmann: Event-Handler zentral registrieren u. Funktionen übernehmen

Hallihallo!

Nach über einer Woche Lese- und Experimentierzeit bin ich leider immer noch nicht imstande, meinem Problem einen einprägsamen Namen zu geben. Zumal ich hier im Forum die Länge des Themas begrenzen muss, aber nicht angezeigt bekomme auf wieviel Zeichen...

Worum geht es?
Auf meinen Seiten sollen Links, deren Target "_top" lautet, nach Anklicken eine Variable auf false setzen. Klingt einfach: onclick="funcSetzeVarFalse();"
Das Target wird aber oft serverseitig aus dem Customizing gesetzt und an die Daten komme ich nicht ran. Ich weiß also einfach nicht, in welchem Link das Target auf "_top" gesetzt wird. Ein einfacher Event-Handler "onclick" in einfach mal jedem Link mit dem Target "_top" scheidet also aus. Auch will ich nicht in jedem der ca. 80 Templates so einen Event-Handler in jedem Link einbauen, auch schon, weil ich den Kunden nicht zumuten will, das nachzurüsten, und weil man dann bei jedem neuen Link darauf achten muss, auch hier diesen Event-Handler einzubauen.
Ich möchte eine elegante Lösung in einem zentralen Skript am Anfang der Seite. Also: Event-Handler zentral registrieren und nicht im Tag.

Klingt einfach.

Was aber ist mit den vielleicht vorhanden onclick-Events, die die Kunden doch noch in ihre Links einbauen? Die muss ich irgendwie auch auslesen und ausführen.
Das hab ich sogar hingekriegt (getestet für IE und FF):

// globales Array mit den evtl. vorhandenen onclick-Funktionen der Links:
var arrOnClickEvents = new Array();

window.onload = setOnclickHref;

function setOnclickHref(){
   var strTagname = "a";
   var strTarget = "_top";
   var objHandlerElement = document.getElementsByTagName(strTagname);
   if(!objHandlerElement){
      return false;
   }
   // durch alle betroffenen Elemente iterieren:
   for(var i=0; i<objHandlerElement.length; i++)
   {
      if(objHandlerElement[i].target == strTarget){
         // Hat dieses Element bereits eine onclick-Funktion?
         // dann schreibe sie in das Array:
         if(objHandlerElement[i].onclick){
            arrOnClickEvents[i] = objHandlerElement[i].onclick;
         }
         // Das lang-Attribut zu setzen ist ein Workaround, damit der Link weiss,
         // welcher Array-Index der richtige ist:
         objHandlerElement[i].lang = i;
         // Hier wird nun eine neue Funktion an das onClick-Event gebunden:
         objHandlerElement[i].onclick = function() {
            if(arrOnClickEvents[this.lang])
               {
                  // eine bereits vorhandene onclick-Funktion wird aus dem Array geholt und ausgefuhert:
                  arrOnClickEventsthis.lang;
               }
               alert(this.target + " " + this.lang);
         } // Ende neue Funktion
      } // Ende if target=strTarget
   } // Ende for
}

Das dazu passende HTML sieht so aus:

<a href="test.html" target="_top" id="hubi1" onclick="this.id = 'gnn'; alert(this.id);">_top und JS</a><br>

<a href="test.html" target="_top" id="hubi2">_top</a><br>
<a href="test.html" target="_blank" id="hubi3">blank</a><br>

Bei Klick auf "hubi1", den ersten Link, erscheint tatsächlich die Alert-Box mit der neuen ID "gnn" und danach die Alert-Box mit dem Target des Links und dem Inhalt des lang-Attributs.

So weit, so schön.

Wenn ich aber beim Link die Funktion beim onclick-Event ändere, z.B. eine irgendwo definierte Funktion aufrufe, dann geht's schief:

...
function alertTest(strWas){
   alert(strWas);
}
...
<a href="test.html" target="_top" id="hubi1" onclick="alertTest('Diese ID: ' + this.id);">_top und JS</a><br>

Also, richtig schief geht's nicht wirklich, die Funktion im Tag wird aufgerufen, aber "this.id" ist undefiniert.

*haarerauf*

Ich bin völlig frustriert, da ich nicht mal ansatzweise erkenne, wo ich was wie drehen muss, damit "this.id" auch wirklich die ID des Links übergibt.
Wieso funktioniert:
this.id = 'gnn'; alert(this.id);
aber das hier nicht:
alertTest('Diese ID: ' + this.id);
???

Dabei habe ich immer im Hinterkopf, dass ich dieses Problem an einer zentralen Stelle lösen können muss, da ich ja nicht weiß, was für onclick-Funktionen die Kunden so in die Templates reinschreiben.

Ich bin für jede Hilfe dankbar! Auch für einen Schubs in die richtige Richtung!
Danke schon mal!

Liebe Grüße
Anja  ;->

  1. hi,

    Ich bin völlig frustriert, da ich nicht mal ansatzweise erkenne, wo ich was wie drehen muss, damit "this.id" auch wirklich die ID des Links übergibt.
    Wieso funktioniert:
    this.id = 'gnn'; alert(this.id);
    aber das hier nicht:
    alertTest('Diese ID: ' + this.id);
    ???

    Wenn du den Aufruf wirklich im onclick-Attribut im HTML notiert hast - dann wir die Funktion im Kontext des Elements ausgeführt.

    Dieser Kontext geht dir aber verloren, wenn du hier

    // Hat dieses Element bereits eine onclick-Funktion?
             // dann schreibe sie in das Array:
             if(objHandlerElement[i].onclick){
                arrOnClickEvents[i] = objHandlerElement[i].onclick;

    die Funktionen alle in dein Array legst.

    // Das lang-Attribut zu setzen ist ein Workaround, damit der Link weiss,
             // welcher Array-Index der richtige ist:
             objHandlerElement[i].lang = i;

    Pfui bah. Warum dazu das lang-Attribut missbrauchen?
    Du kannst auch problemlos eigene Eigenschaften an Javascript-Objekte anhängen, bspw. objHandlerElement[i].arrOnClickEventsIndex = i;

    // Hier wird nun eine neue Funktion an das onClick-Event gebunden:
             objHandlerElement[i].onclick = function() {
                if(arrOnClickEvents[this.lang])
                   {
                      // eine bereits vorhandene onclick-Funktion wird aus dem Array geholt und ausgefuhert:
                      arrOnClickEventsthis.lang;

    call bzw. apply könnten dir hier helfen, die Funktion auch wirklich im Kontext des ursprünglichen Elementes/Objektes auszuführen.

    Btw: Diverse Ansätze für sowas sind bereits entwickelt worden, bspw. http://ejohn.org/projects/flexible-javascript-events/

    gruß,
    wahsaga

    --
    /voodoo.css:
    #GeorgeWBush { position:absolute; bottom:-6ft; }
    1. Huhu!

      oh, wow, seid Ihr schnell! Danke, Cheatah und wahsaga, ich werde mir mal die angegebenen Quellen zu Gemüte führen. Leider fehlt mir komplett der theoretische Hintergrund zu modernem Javascript, aber, hey, ich habe auch den anderen Kram hingewurschtelt bekommen.... :) .. auch wenn ich mich immer dabei fühle, als ob ich mit etwas rumspiele, dass ein bisschen zu groß ist...
      Danke nochmal!

      Liebe Grüße
      Anja ;->

      1. gruss Anja,

        oh, wow, seid Ihr schnell! Danke, Cheatah und wahsaga, ich werde mir
        mal die angegebenen Quellen zu Gemüte führen.

        dies ist zur loesung Deines problems aber nicht unbedingt notwendig.
           sowohl die JavaScript- als auch die DOM-API stellen Dir robuste
           werkzeuge zur verfuegung.

        Deinem beispiel mangelt es eigentlich nur an der w3c-DOM-methode
           [addEventListener] bzw. derem microsoftschen pendant [attachEvent]
           sowie einer event-auswertung innerhalb der den links hinzuzufuegenden
           methode.
           wenn Du Dich jetzt noch einiger array-iteratoren bedientest, kaeme
           die kernfunktion zur loesung Deines problems auf gerademal 18 Zeilen
           code in einer eleganten aussagekraeftigen schreibweise.

        das folgende bsp. dient zur bekraeftigung des gerade gesagten - der
           letzte code-block registriert die hauptfunktion "addOnclickListener"
           auf den "onload"-event des [window]-objekts. "addOnclickListener"
           wiederum registriert auf allen links, deren target-attribut-wert
           gleich "_top" ist den von Dir frei zu gestaltenden "oncklick"-
           eventhandler "onclickHandler".

        die ersten beiden Array-methoden stellen in allen browsern, die
           vefuegbarkeit der statischen methode [Array.filter] sowie die der
           array-methode [forEach] sicher - ACHTUNG CODE:

        <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html401/loose.dtd">  
        <html>  
          
          <head>  
            <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">  
          
            <title>addOnclickListener.html</title>  
          
            <meta http-equiv="content-script-type" content="text/javascript">  
            <!--  
              fuer alle browser, die folgend verlinkte methoden (noch) nicht implementieren -  
              [link:http://developer.mozilla.org/en/docs/New_in_JavaScript_1.6#Array_extras]  
          
              bietet sich - ebenfalls verlinkt - hier eine der moeglichen alternativen an:  
              [link:http://www.pseliger.de/jsExtendedApi/jsApi.bundles.DOM.getters.js]  
            //--><!--  
            <script type="text/javascript" src="...[path].../jsApi.bundles.DOM.getters.js">//-->  
            <script type="text/javascript">
        ~~~~~~javascript
          
          
              if (typeof Array.prototype.forEach != "function") {  
          
                Array.prototype.forEach = function (fct, thisArr) {  
          
                  if (typeof fct == "function") {  
                    thisArr = ((thisArr && (typeof thisArr == "object") && (thisArr instanceof Array)) ? (thisArr) : (null));  
                    var i, l = this.length;  
                    for (i=0; i<l; ++i) {  
                      fct.call(thisArr, this[i], i, this);  
                    }  
                  }  
                };  
              }  
              Array.filter = function (obj, fct) {  
          
                var arr = [];  
                if (typeof fct == "function") {  
                  var i, l = (((obj instanceof Array) || ((typeof obj.length == "number") && ((typeof obj.item == "function") || (typeof obj.item == "object") || (typeof obj.item == "string") || (obj instanceof window.NodeList) || (obj instanceof window.HTMLCollection)))) ? (obj.length) : (0));  
                  for (i=0; i<l; ++i) {  
                    if (fct.call(null, (obj[i] || obj.item(i)), i, obj)) {  
                      arr.push(obj[i] || obj.item(i));  
                    }  
                  }  
                }  
                return arr;  
              };  
          
          
              var onclickHandler = function (evtObj) {  
          
                evtObj = (window.event || evtObj); //  [MouseEvent]  
                var evtSrc = (evtObj.srcElement || evtObj.target); // [HTMLLinkElement]  
          
                alert("evtSrc - this object : " + evtSrc + "\nevtSrc.nodeName : " + evtSrc.nodeName + "\nevtSrc.id : " + evtSrc.id + "\n\nevtSrc.onclick: " + evtSrc.onclick);  
              };  
          
          
              var addOnclickListener = function () {  
          
                if (addOnclickListener.isInitialized) {return;} // check due to opera;  
                addOnclickListener.isInitialized = true;  
          
                Array.filter(document.links, (function (elm/*, idx, arr*/) {  
          
                  return (elm.target == "_top");  
          
                })).forEach(function (elm/*, idx, arr*/) {  
          
                  if (elm.addEventListener) {  
                    elm.addEventListener("click", onclickHandler, false); // [false]-flag due to opera;  
                  } else if (elm.attachEvent) {  
                    elm.attachEvent("onclick", onclickHandler);  
                  }  
                });  
              };  
          
          
              if (window.addEventListener) {  
                window.addEventListener("load", addOnclickListener, true);  
              } else if (window.attachEvent) {  
                window.attachEvent("onload", addOnclickListener);  
              }
        ~~~~~~html
          
          
            </script>  
          </head>  
          
          <body>  
            <ul>  
              <li><a href="#" target="_top" id="hubi1" onclick="this.id = 'gnn'; alert(this.id);">_top und JS</a></li>  
              <li><a href="#" target="_top" id="hubi2">_top</a></li>  
              <li><a href="#" target="_blank" id="hubi3">blank</a></li>  
            </ul>  
          </body>  
          
        </html>
        

        so long - peterS. - pseliger@gmx.net

        --
        »Because objects in JavaScript are so flexible, you will want to think differently about class hierarchies.
        Deep hierarchies are inappropriate. Shallow hierarchies are efficient and expressive.« - Douglas Crockford
        ie:( fl:) br:> va:( ls:& fo:) rl:| n3;} n4:} ss:} de:µ js:} mo:? zu:]
        1. hallo again Anja,

          zum geaenderten thema gesellt sich noch folgende korrektur:

          ... kaeme die kernfunktion zur loesung Deines problems auf gerademal
          18 Zeilen code in einer eleganten aussagekraeftigen schreibweise.

          netto sind es sogar nur 9 zeilen.

          so long - peterS. - pseliger@gmx.net

          --
          »Because objects in JavaScript are so flexible, you will want to think differently about class hierarchies.
          Deep hierarchies are inappropriate. Shallow hierarchies are efficient and expressive.« - Douglas Crockford
          ie:( fl:) br:> va:( ls:& fo:) rl:| n3;} n4:} ss:} de:µ js:} mo:? zu:]
          1. hi,

            netto sind es sogar nur 9 zeilen.

            Muss man jetzt sogar schon JavaScript-Code versteuern?

            Tzz, die Raffgier dieses Staates kennt echt keine Grenzen mehr.

            gruß,
            wahsaga

            --
            /voodoo.css:
            #GeorgeWBush { position:absolute; bottom:-6ft; }
    2. Huhu!

      YES! Jippie, es funktioniert! Danke, danke, danke, der Tipp mit .call() war genau richtig!

      Wieso funktioniert:
      this.id = 'gnn'; alert(this.id);
      aber das hier nicht:
      alertTest('Diese ID: ' + this.id);
      ???

      Wenn du den Aufruf wirklich im onclick-Attribut im HTML notiert hast - dann wir die Funktion im Kontext des Elements ausgeführt.

      Sowas hatte ich mir schon gedacht, dass halt "this" beim Aufruf nicht genau referenziert ist. Mich wunderte nur, dass es im ersten Fall klappt und im zweiten nicht. Für mich sieht beides nämlich fast gleich aus... ja, gut, das eine ist ein Funktionsaufruf, das andere nicht, aber bei beiden geht es um "this". Da muss ich wohl noch viiiel lernen...

      Dieser Kontext geht dir aber verloren, wenn du hier

      // Hat dieses Element bereits eine onclick-Funktion?
          // dann schreibe sie in das Array:
          if(objHandlerElement[i].onclick){
             arrOnClickEvents[i] = objHandlerElement[i].onclick;

      die Funktionen alle in dein Array legst.

      Ja, aber nur im zweiten Fall, im ersten nicht. Das hat mich doch sehr verwundert.

      // Das lang-Attribut zu setzen ist ein Workaround, damit der Link weiss,
          // welcher Array-Index der richtige ist:
          objHandlerElement[i].lang = i;

      Pfui bah. Warum dazu das lang-Attribut missbrauchen?

      Ja, ich weiß und du hast vollkommen recht. *schäm* Aber ich wollte erstmal das eine Problem lösen, bevor ich mir Gedanken mache, wie ich denn ein eigenes Attribut erfinde.

      Du kannst auch problemlos eigene Eigenschaften an Javascript-Objekte anhängen, bspw. objHandlerElement[i].arrOnClickEventsIndex = i;

      Danke! Das habe ich dann auch so gemacht.

      [...]

      call bzw. apply könnten dir hier helfen, die Funktion auch wirklich im Kontext des ursprünglichen Elementes/Objektes auszuführen.

      Ja, ja, ja, das war es!
      Hier nochmal der funktionierende Code:

      var arrOnClickEvents = new Array();

      window.onload = setOnclickHref;

      function setOnclickHref(){
         var strTagname = "a";
         var strTarget = "_top";
         var objHandlerElement = document.getElementsByTagName(strTagname);
         if(!objHandlerElement){
            return false;
         }
         // durch alle betroffenen Elemente iterieren:
         for(var i=0; i<objHandlerElement.length; i++)
         {
            if(objHandlerElement[i].target == strTarget){
               // Hat dieses Element bereits eine onclick-Funktion?
               // dann schreibe sie in das Array:
               if(objHandlerElement[i].onclick){
                  arrOnClickEvents[i] = objHandlerElement[i].onclick;
               }
               // Neues Attribut setzen, damit der Link weiss, welcher Array-Index der richtige ist:
               objHandlerElement[i].intIndexForArrOnClickEvents = i;
               // Hier wird nun eine neue Funktion an das onClick-Event gebunden:
               objHandlerElement[i].onclick = function() {
                  if(arrOnClickEvents[this.intIndexForArrOnClickEvents])
                  {
                     // eine bereits vorhanden onclick-Funktion wird aus dem Array geholt und ausgefuehrt:
                     arrOnClickEvents[this.intIndexForArrOnClickEvents].call(this);
                  }
                  alert(this.target + " " + this.intIndexForArrOnClickEvents);
               } // Ende neue Funktion
            } // Ende if target=strTarget
         } // Ende for
      }

      Also, ob das nun so schlau ist wie ich das hier mache, das sei mal dahingestellt. Immerhin verstehe ich zumindest was ich da mache. :) Anders bei dem Code aus der Quelle:

      Btw: Diverse Ansätze für sowas sind bereits entwickelt worden, bspw. http://ejohn.org/projects/flexible-javascript-events/

      Da weiß ich leider nur ungefähr, was da passiert und gar nicht, wie ich das wo genau in meiner Funktion einbaue. Und da ich schon über ne Woche experimentiere, wollte ich schnelle Ergebnisse. Und jetzt klappt's! *poing poing poing* Meine Woche ist gerettet!

      Vielen, vielen Dank nochmal!

      ... jetzt muss ich's nur noch im Safari testen...

      Liebe Grüße
      Anja  ;->

  2. Hi,

    Klingt einfach.

    jo :-)

    Was aber ist mit den vielleicht vorhanden onclick-Events, die die Kunden doch noch in ihre Links einbauen? Die muss ich irgendwie auch auslesen und ausführen.
    Das hab ich sogar hingekriegt (getestet für IE und FF):

    // globales Array mit den evtl. vorhandenen onclick-Funktionen der Links:

    Denke objektorientiert. Die onclick-Eigenschaft eines Links ist die onclick-Eigenschaft eines Links, nicht eine onclick-Eigenschaft der Seite. Warum also sollte sie außerhalb des einen Links existieren?

    if(objHandlerElement[i].target == strTarget){
             // Hat dieses Element bereits eine onclick-Funktion?
             // dann schreibe sie in das Array:

    Nein, dann rette sie in eine von Dir definierte Eigenschaft des selben Elements.

    Also, richtig schief geht's nicht wirklich, die Funktion im Tag wird aufgerufen, aber "this.id" ist undefiniert.

    Du hast "this" auf ein anderes Objekt umgelegt, nämlich auf das window.

    *haarerauf*

    Lass das, das bringt nichts. Glaub mir. Das bisschen Ersparnis an Shampoo lohnt nicht.

    Ich bin völlig frustriert, da ich nicht mal ansatzweise erkenne, wo ich was wie drehen muss, damit "this.id" auch wirklich die ID des Links übergibt.

    Schau Dir in anderen Systemen wie z.B. prototype.js an, wie derlei Dinge gelöst werden. Dort etwa ist die Methode Function.bind() interessant.

    Wieso funktioniert:
    this.id = 'gnn'; alert(this.id);
    aber das hier nicht:
    alertTest('Diese ID: ' + this.id);

    Oh, das funktioniert gleichermaßen, wenn Du es richtig schreibst:

    <foo onclick="this.id = 'gnn'; alert(this.id);">
    kontra
    <foo onclick="alertTest('Diese ID: ' + this.id);">

    Etwas komplett anderes ist aber

    function() { alertTest('Diese ID: ' + this.id); }

    Cheatah

    --
    X-Self-Code: sh:( fo:} ch:~ rl:° br:> n4:& ie:% mo:) va:) de:] zu:) fl:{ ss:) ls:~ js:|
    X-Self-Code-Url: http://emmanuel.dammerer.at/selfcode.html
    X-Will-Answer-Email: No
    X-Please-Search-Archive-First: Absolutely Yes
    1. hi,

      Denke objektorientiert. Die onclick-Eigenschaft eines Links ist die onclick-Eigenschaft eines Links, nicht eine onclick-Eigenschaft der Seite. Warum also sollte sie außerhalb des einen Links existieren?

      if(objHandlerElement[i].target == strTarget){
               // Hat dieses Element bereits eine onclick-Funktion?
               // dann schreibe sie in das Array:

      Nein, dann rette sie in eine von Dir definierte Eigenschaft des selben Elements.

      Wo du das gerade ansprichst - in der Diskussion zur von mir verlinkten Herangehensweise spricht ein Kommentierer diesbezüglich von "Polluting the DOM":

      "Why I like this version better than the one above? Well, all adapted functions (the event handlers) are stored in one central place. No more polluting the DOM."

      Ich konnte das als Pro-Argument für eine zentrale Verwaltung aller Handler-Funktionen bisher nicht nachvollziehen.
      Das DOM ist doch schon per se "polluted" - HTML-Elemente haben zahlreiche (/-lose) Eigenschaften, die in ihrer DOM-Repräsentation eh an ihnen aufgehängt sind.
      Deshalb sehe ich nicht, wo das Problem liegen soll, wenn man für ein eigenes Eventhandling oder auch andere Zwecke Element-spezifische Werte nicht auch am Element ablegen sollte.

      Höchstens einen potentiellen Namenskonflikt mit aktuellen, mir als Scriptersteller aber unbekannten, oder zukünftigen, Browser-eigenen oder in späteren Javascript-/ECMA-/DOM-Versionen noch definierten werdenden [HTML-]Objekt-Eigenschaften könnte ich mir als Argument für diese Sichtweise noch vorstellen.
      Aber dann dürfte ich nicht mal mehr eigene Funktionen oder Objekte im Kontext von window anlegen - window könnte ja auch zukünftig mit einer gleichnamigen Methode/Eigenschaft erweitert werden, die mir dann des gleiche Problem bereitet.

      gruß,
      wahsaga

      --
      /voodoo.css:
      #GeorgeWBush { position:absolute; bottom:-6ft; }
      1. Hi,

        Wo du das gerade ansprichst - in der Diskussion zur von mir verlinkten Herangehensweise spricht ein Kommentierer diesbezüglich von "Polluting the DOM":

        [...]

        Aber dann dürfte ich nicht mal mehr eigene Funktionen oder Objekte im Kontext von window anlegen - window könnte ja auch zukünftig mit einer gleichnamigen Methode/Eigenschaft erweitert werden, die mir dann des gleiche Problem bereitet.

        jo, eben. Damit hat der Autor o.g. Kommentares den einzigen[1] Grund für sein Argument gleich ad absurdum geführt. Gerade in einer objektorientierten Welt, und gerade beim Prototype-Ansatz von JavaScript halte ich alles andere als das Speichern eines objektbezogenen Wertes im Objekt selbst für ... nun, für Anfängertum. Ich fahre mehr und mehr den Ansatz, insbesondere das window-Objekt frei von allem zu halten, wann immer es geht.

        Cheatah

        [1] Zumindest fällt mir kein anderer ein :-)

        --
        X-Self-Code: sh:( fo:} ch:~ rl:° br:> n4:& ie:% mo:) va:) de:] zu:) fl:{ ss:) ls:~ js:|
        X-Self-Code-Url: http://emmanuel.dammerer.at/selfcode.html
        X-Will-Answer-Email: No
        X-Please-Search-Archive-First: Absolutely Yes