ChrisPHL: IE7 akzeptiert (zu Recht) 'onchange'-Attribut im form-Tag nicht

Moin moin,

ich habe ein mehrseitiges Eingabeformular, bei dem ich auf jeder Seite mit onchange im form-Tag Änderungen abfange, damit ich beim Klick auf einen anderen Menüpunkt noch schnell bei Änderungen nachfragen kann, ob denn die aktuelle Seite noch gepostet werden sollen ,damit die Daten gespeichert werden.
Nun habe ich mich eben mal schlau gemacht und musste feststellen, dass das onchange-Attribut gar nicht im form-Tag vorgesehen ist (Leider gilt das wohl auch für das div-Tag.). Es wird zwar von Firefox und Opera unterstützt aber der IE (hier habe ich Version 7) akzeptiert das nicht.

Ich möchte aber gern Änderungen an den Eingabefeldern registrieren, ohne an jedes Feld ein onchange anhängen zu müssen.

Hat dazu jemand eine Idee, kennt jemand einen 'Workaround'?

Gruß
Christian

  1. Hallo!

    Das onchange-Event funktioniert ohnehin nur mit JavaScript - also kannst Du auch JavaScript verwenden, um es ggf. auf die Formularelemente zu verteilen, etwa so:

    if (document.all && !window.opera) {  
       for (var f in document.forms) {  
          for (var e in f.elements) {  
             e.onchange = function(f) { return function() { f.onchange(); }}(f);  
          }  
       }  
    }
    

    Ggf. kannst Du sicherheitshalber mit try/catch arbeiten, um Fehler zu vermeiden, falls Elemente in forms.elements rutschen, die kein onchange-Event vertragen können.

    Gruß, LX

    --
    RFC 1925, Satz 3: Mit ausreichendem Schub fliegen Schweine wunderbar. (...)
    1. if (document.all && !window.opera) {

      Das ist eine ziemlich schlechte Browsererkennung.
      Conditional Compilation wäre angemessener.

      /*@cc_on
      ...
      @*/

      Allerdings ist es schwierig, die Unterstützung von change beim Formularelement festzustellen, da kann man wohl nur negativ vorgehen und die Sache für die Browser nachbauen, von denen man weiß, dass sie gerade kein change beim Form feuern...

      for (var f in document.forms) {
            for (var e in f.elements) {

      Knotenlisten (HTMLCollection in DOM 2 HTML bzw. HTML 5) sollte man nicht mit for..in durchlaufen, da bekommt man unter Umständen Member wie length, item und namedItem und weitere irrelevante zurück.

      e.onchange = function(f) { return function() { f.onchange(); }}(f);

      Das ist ganz clever, nur sollte die Funktion nicht für jedes Element neu erzeugt werden, sondern in der äußeren document.forms-Schleife.

      Ggf. kannst Du sicherheitshalber mit try/catch arbeiten, um Fehler zu vermeiden

      Nein, DAS sollte man so gut wie NIE tun. Exceptions vermeidet man, indem man vorausschauend programmiert.

      falls Elemente in forms.elements rutschen, die kein onchange-Event vertragen können.

      Jedes Elementobjekt verträgt, dass man irgendwelche Member anlegt. Das ist nicht das Problem.

      Mathias

      1. Allerdings ist es schwierig, die Unterstützung von change beim Formularelement festzustellen, da kann man wohl nur negativ vorgehen und die Sache für die Browser nachbauen, von denen man weiß, dass sie gerade kein change beim Form feuern...

        Ich würde vermutlich anders vorgehen: Nicht mit dem Feuern von change-Events beim Formularelement in der Bubbling-Phase rechnen, denn das ist meines Wissens nirgendwo spezifiziert - auch wenn es einige Browser tun. (In HTML 5 gibts dafür den Event »formchange«, den allerdings noch kein Browser unterstützt, soweit ich weiß.)

        Die Nicht-IE-Browser kennen Capturing von change-Ereignissen:

        formularElement.addEventListener("change", handler, true);

        IE kennt addEventListener nicht und auch kein Capturing, für den ist dein Ansatz als Fallback wie gesagt sehr clever (zumindest fällt mir gerade kein besserer ein). Der Witz ist halt, dass es »sicher« ist, im else-Zweig von if (formularElement.addEventListener) deinen Fix anzuwenden, weil ausgeschlossen ist, dass der change-Handler dann zweimal ausgeführt wird. (Es kann ja sein, dass ein kommender IE sich wie die anderen Browser verhält, daher ist eine Browsererkennung schlecht, eine Fähigkeitenerkennung besser.)

        Mathias

        1. (In HTML 5 gibts dafür den Event »formchange«, den allerdings noch kein Browser unterstützt, soweit ich weiß.)

          Korrektur: formchange war Teil von Web Forms 2 und ist bereits im Opera (soweit ich weiß ab Version 9) implementiert.

          1. Also vielen Dank für die interessanten Informationen, ich sehe doch, dass es auch für Leute, die sich deutlich besser als ich auskennen, immer noch schwierig genug ist, sich durch den Browser- und Standardsjungle zu schlagen.
            Ich löse mein Problem jetzt allerdings völlig anders:
            Da ich ohnehin alle meine Elemente mit selbst erstellten php-Makros erzeuge, habe ich einfach den benutzten Makros ein
            onclick="if (navigator.appName=='Microsoft Internet Explorer'){this.blur(); document.getElementById('".$die_per_php_vergebene_id."').focus(); }" onblur="if (navigator.appName=='Microsoft Internet Explorer'){ javascript:formtainted(); }"
            hinzugefügt. Das führt zu einem fast identischen Verhalten im IE. Es markieren lediglich auch nicht veränderte Felder das 'Form' schon als 'tainted'. (Es genügt dann eben schon, wenn sie angeklickt wurden.)

            1. onclick="if (navigator.appName=='Microsoft Internet Explorer'){this.blur(); document.getElementById('".$die_per_php_vergebene_id."').focus(); }" onblur="if (navigator.appName=='Microsoft Internet Explorer'){ javascript:formtainted(); }"

              Das war das, was ich mit zuviel des Guten gemeint hatte.

              Struppi.

        2. Hallo Ingrid, lange nicht mit dir gesprochen!

          Ich würde vermutlich anders vorgehen: Nicht mit dem Feuern von change-Events beim Formularelement in der Bubbling-Phase rechnen, denn das ist meines Wissens nirgendwo spezifiziert

          Das ist Unsinn, natürlich ist für change-Events spezifiziert, dass sie aufsteigen.
          Sie tun es nur im IE aus unerfindlichen Gründen nicht.

          http://www.quirksmode.org/dom/events/change.html
          http://www.w3.org/TR/DOM-Level-3-Events/events.html#event-change

          Sorry für die Konfusion.

      2. e.onchange = function(f) { return function() { f.onchange(); }}(f);

        Das ist ganz clever, nur sollte die Funktion nicht für jedes Element neu erzeugt werden, sondern in der äußeren document.forms-Schleife.

        Eigentlich muss sie insgesamt nur einmal erzeugt werden, weil Closures hier die Sache m.M.n. nicht signifikant vereinfachen. Man kann auch einfach this.form.onchange() schreiben, dann verbessert man die Performanz und spart auch Komplexität ein. (Was der obige Code macht und warum man Closures in Schleifen so notieren muss, versteht glaube ich niemand außer ein paar JS-Cracks.)

        Mathias

    2. Hallo!

      Das onchange-Event funktioniert ohnehin nur mit JavaScript - also kannst Du auch JavaScript verwenden, um es ggf. auf die Formularelemente zu verteilen, etwa so:

      if (document.all && !window.opera) {

      for (var f in document.forms) {
            for (var e in f.elements) {
               e.onchange = function(f) { return function() { f.onchange(); }}(f);
            }
         }
      }

        
      Ich hab das mal umgesetzt (inkl. den Hinweisen von Mathias)  
        
      ~~~html
      <form onchange="alert(this.name)" name="formular">  
      <input type="text" name="a" >  
      <input type="text" name="b" >  
      </form>  
      <script type="text/javascript">
      
      if (document.all && !window.opera) {  
        
         for (var i = 0;i <document.forms.length; i++) {  
         var f = document.forms[i];  
            for (var j = 0; j < f.elements.length;j++) {  
      		var e = f.elements[j];  
      		e.onchange = function() { eval( f.onchange);};  
            }  
         }  
      }
      

      </script>
      onchange läßt sich leider nicht direkt aufrufen, da es der IE als String sieht (eine Möglichkeit rauszufinden ob der Browser onchange im unterstützt). Schlecht ist, das this in dem Fall das Element ist.

      Das läßt sich umgehen:

      var func = f.onchange.replace(/\bthis\b/g, '_this');  
      e.onchange = function() { eval("var _this = this.form; " + func + "");};  
      
      

      Ich weiß aber nicht, ob das nicht des guten zuviel ist.

      Struppi.

      1. for (var i = 0;i <document.forms.length; i++) {
           var f = document.forms[i];
              for (var j = 0; j < f.elements.length;j++) {
        var e = f.elements[j];
        e.onchange = function() { eval( f.onchange);};

        Ich sags ungerne: f zeigt nach Ablauf der Schleife immer auf das letzte Formular.
        Closures und Schleifen sind ein Fallstrick, den man am besten umgeht.

        das this in dem Fall das Element ist.

        Zum Thema »zuviel des Guten« habe ich natürlich etwas beizusteuern ;-)

        <!DOCTYPE html>
        <form id="formular1" onchange="alert(this.id)">
        <input type="text" name="a">
        <input type="text" name="b">
        </form>
        <form id="formular2" onchange="alert(this.id)">
        <input type="text" name="a">
        <input type="text" name="b">
        </form>
        <form id="formular3">
        <input type="text" name="a">
        <input type="text" name="b">
        </form>
        <form id="formular4">
        <input type="text" name="a">
        <input type="text" name="b">
        </form>
        <script type="text/javascript">

        /* Es sollte auch möglich sein, die Handler-Funktion traditionell zu registrieren */
        document.getElementById("formular3").onchange = function () {
           alert(this.id);
        };
        /* Ich überlege mir noch was für attachEvent ;) */

        /*@cc_on
        window.onload = function () { // Oder wo und wie man die Funktion auch immer starten will
           // Ein generischer Handler außerhalb der Schleifen.
           // Hier könnte man natürlich noch Daten wie this und event übergeben.
           var handler = function () { this.form.onchange(); };
           for (var i = 0; i < document.forms.length; i++) {
              var f = document.forms[i];
              if (typeof f.onchange == "string") {
                 // Anstatt eval zu wiederholen, übersetzen wir den Code nur einmal.
                 // Der Kontext stimmt durch den Aufruf über this.form.onchange()
                 f.onchange = new Function(f.onchange);
              }
              if (typeof f.onchange == "function") {
                 for (var j = 0; j < f.elements.length; j++) {
                    f.elements[j].onchange = handler;
                 }
              }
           }
        };
        @*/
        </script>

        Mathias