NetizenKane: Dynamische Button-Events mit korrektem Bezug

Hallo,

folgende Aufgabenstellung liegt vor mir:
Es sollen in einer Tabelle, die innerhalb eines Formulars liegt, Tabellenzeilen mit je vier Zellen per Buttonclick hinzugefügt werden. Die neuen Zellen sollen ein Textfeld für freie Eingaben enthalten, ein weiteres Textfeld für Farb-Hexadezimalcode, einen Button, der wiederum einen DHTML-Colorpicker öffnet und ein Textfeld als Farbanzeigefeld. Sobald man eine Farbe ausgewählt hat, soll der entsprechende Hexadezimalcode im Farbcode-Textfeld erscheinen und sich die Farbe im Anzeigefeld entsprechend ändern.

Soweit, so gut. Durch Internetrecherche bin ich einigermassen vorangekommen. Bis hierher sieht mein Code ungefähr so aus:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
 "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">

<script type="text/javascript" src="colorpicker.js"></script>
<script type="text/javascript">
<!--
var rowcount = 1;

function new_field()
{
 var row = document.getElementById('form_table').insertRow(rowcount);
 var cell_1 = row.insertCell(0);
 var cell_2 = row.insertCell(1);
 var cell_3 = row.insertCell(2);

var text = document.createTextNode('Option ' + (rowcount + 1) + ' ');
 cell_1.appendChild(text);

var input = document.createElement('input');
 input.type = 'text';
 input.name = 'option[]';
 input.className = 'inputbox autowidth';
 input.size = 60;
 input.maxlength = 150;
 cell_1.appendChild(input);

var text = document.createTextNode('max. ');
 cell_2.appendChild(text);

var input = document.createElement('input');
 input.type = 'text';
 input.name = '_max[]';
 input.className = 'inputbox autowidth';
 input.size = 5;
 input.maxlength = 4;
 cell_2.appendChild(input);

var text = document.createTextNode('Color ');
 cell_3.appendChild(text);

var input = document.createElement('input');
 input.type = 'text';
 input.name = 'color[]';
 input.id = 'color_' + rowcount + '';
 input.className = 'inputbox autowidth';
 input.size = 10;
 input.maxlength = 7;
 cell_3.appendChild(input);

var input = document.createElement('input');
 var pickColor_string = "pickColor('color_' + (rowcount-1) +'', 'colorwatch_' + (rowcount-1) +'');";
 var pickColor = new Function(pickColor_string);
 input.type = 'button';
 input.name = 'pick[]';
 input.id = 'pick_' + rowcount + '';
 input.className = 'button2';
 input.value = 'Pick';
 input.onclick = pickColor;
 cell_3.appendChild(input);

var input = document.createElement('input');
 input.type = 'text';
 input.name = 'colorwatch[]';
 input.id = 'colorwatch_' + (rowcount) + '';
 input.className = 'inputbox autowidth';
 input.size = 1;
 input.maxlength = 0;
 input.readOnly = true;
 cell_3.appendChild(input);

rowcount++;
}
//-->
</script>

</head>
<body>

<form name="posting" action="save.php" method="post">
 <div id="colorpicker" class="colorpicker"></div>
 <table id="form_table" border="1" cellspacing="0" cellpadding="0">
    <tr>
     <td>Option 1 <input name="option[]" id="option_0" type="text" size="60" maxlength="150" class="inputbox autowidth" /></td>
     <td>max. <input size="5" maxlength="4" name="max[]" id="max_0" class="inputbox autowidth" /></td>
     <td>Color <input size="10" maxlength="7" name="color[]" id="color_0" class="inputbox autowidth" /><input type="button" name="pick[]" onclick="pickColor('color_0', 'colorwatch_0');" value="Pick" class="button2" /><input type="text" size="1" maxlength="0" name="colorwatch[]" id="colorwatch_0" class="inputbox autowidth" readonly="readonly" /></td>
    </tr>
  <tr>
   <td colspan="5"><input type="button" onclick="new_field();" value="Add Row" /></td>
  </tr>
 </table>
</form>

</body>
</html>

Leider kommt es dabei zu einem seltsamen Verhalten. Wenn ich bei mehreren Zeilen über einen der dynamischen Buttons (also alle ab Zeile 2 bis zur vorletzten Zeile) die Farbe auswähle, dann werden die Werte und Farben nicht in den dazugehörigen Feldern dargestellt, sondern in denen der letzten Zeile.

Man kann dieses Verhalten auf folgender Seite mal betrachten:
http://www.clancodes.de/test.html

Ich hab mich mal etwas umgeschaut und folgenden Link mit einem ähnlichen Problem gefunden. Allerdings komme ich mit der Lösung nicht wirklich klar.
http://forum.jswelt.de/javascript/36676-addeventlistener-mit-dynamischem-aufruf.html

Ich bin für jede Hilfe und jeden Rat mehr als dankbar ;-)
Viele Grüsse
Kane

  1. Hallo NetizenKane,

    Leider kommt es dabei zu einem seltsamen Verhalten. Wenn ich bei mehreren Zeilen über einen der dynamischen Buttons (also alle ab Zeile 2 bis zur vorletzten Zeile) die Farbe auswähle, dann werden die Werte und Farben nicht in den dazugehörigen Feldern dargestellt, sondern in denen der letzten Zeile.

    var pickColor_string = "pickColor('color_' + (rowcount-1) +'', 'colorwatch_' + (rowcount-1) +'');";
    var pickColor = new Function(pickColor_string);
    input.type = 'button';

    input.id = 'pick_' + rowcount + '';

    input.onclick = pickColor;

    Versuch es doch mal stattdessen so:

      
      
    input.rowcount = rowcount-1;  
    input.onclick = function () {  
        pickColor(this.id, 'colorwatch_' + this.rowcount);  
    }
    

    Gruß Gernot

    1. Leider der gleiche Fehler: http://www.clancodes.de/test2.html

      Vermutlich ist das hier das Problem:

      Es handelt sich dabei um ein Referenzierungsproblem, das einer Eigenart Javascripts zugrunde liegt, die ich als Designfehler bezeichnen würde.

      Obwohl Du in der Funktion addcharthandler die Variable id stets neu instanzierst, zeigen alle Handler-Parameter später auf die letzte Instanz dieser Variable.
      Ich vermute mal, dass es damit zusammenhängt, dass dynamische Funtionen erst dann instanziert werden, wenn sie aufgerufen werden. D. h., sie sind bei der Übergabe an addEventListener lediglich Code und noch keine Objekte, wodurch es dann zu dem Referenzierungsproblem kommt.

      Abhilfe kannst Du schaffen, indem Du die Zuweisung der EventHandler in eine separate Funktion auslagerst. Nun steht der wert von id in einem anderen Kontext und wird so übergeben, wie man es erwarten würde.

      Viel schlauer macht mich das aber auch nicht.

      1. Hallo NetizenKane,

        Ich glaube ja, dass der Fehler gar nicht so sehr in deinem Code liegt, als vielmehr in dem, den du dir angeeignet hast

        In der Funktion colorPicker() steht ja z.B. sowas:

        <a class='o5582n66' href='javascript:onclick=putOBJxColor2("+xl+")'>

        Was das "onclick=" nach dem "javascript:" zu suchen hat, ist mir schleierhaft. Auch sonst scheint mir das Script nicht ganz koscher, es arbeitet viel zu sehr mit Strings, über die dann mit getElementById() wieder auf Objekte zugegriffen wird.

        Warum nicht gleich die Objekte, den Button und sein Anzeigefeld miteinander bekannt machen, sich gegenseitig in Eigenschaften als Referenzen verpassen und dann an die entsprechend zu modifizierende colorPicker-Funktion die entsprechnden Objekte übergeben, die in diese dann auch zurückschreibt, anstelle von Strings, die dann wieder über getElementById Objekte ansprechen. Das ist so alles andere als objektorientiert und von hinten durch die Brust ins Auge.

        Gruß Gernot

        1. Hallo Gernot,

          ich habe jetzt mal deinen Rat befolgt und das "fremde" Colorpicker-Skript (das bei näherer Betrachtung wirklich hanebüchen ist) ersetzt durch ein einfaches Testskript. Blöderweise bleibt der Effekt der gleiche:

          http://www.clancodes.de/test3.html

          Code:
          ######################################################################
          <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
           "http://www.w3.org/TR/html4/loose.dtd">
          <html>
          <head>
          <meta http-equiv="content-type" content="text/html; charset=iso-8859-1">

          <script type="text/javascript">
          <!--
          var rowcount = 1;

          function new_field()
          {
           var row = document.getElementById('form_table').insertRow(rowcount);
           var cell_1 = row.insertCell(0);
           var cell_2 = row.insertCell(1);

          var text = document.createTextNode('Option ' + (rowcount + 1) + ' ');
           cell_1.appendChild(text);

          var input = document.createElement('input');
           input.type = 'text';
           input.name = 'option[]';
           input.size = 60;
           input.maxlength = 150;
           cell_1.appendChild(input);

          var text = document.createTextNode('Color ');
           cell_2.appendChild(text);

          var input = document.createElement('input');
           input.type = 'text';
           input.name = 'color[]';
           input.id = 'color_' + rowcount + '';
           input.size = 10;
           input.maxlength = 7;
           cell_2.appendChild(input);

          var input = document.createElement('input');
           var pickColor_string = "pickColor('color_' + (rowcount-1) +'', 'colorwatch_' + (rowcount-1) +'');";
           var pickColor = new Function(pickColor_string);
           input.type = 'button';
           input.name = 'pick[]';
           input.value = 'Pick';
           input.rowcount = rowcount-1;
           input.onclick = function () { setColor('color_' + (rowcount-1) + ''); }
           cell_2.appendChild(input);

          rowcount++;
          }

          function setColor(id)
          {
           document.getElementById(id).value = "Farbe";
          }
          //-->
          </script>

          </head>
          <body>

          <form name="posting" action="save.php" method="post">
           <table id="form_table" border="1" cellspacing="0" cellpadding="0">
              <tr>
               <td>Option 1 <input name="option[]" id="option_0" type="text" size="60" maxlength="150" /></td>
               <td>Color <input size="10" maxlength="7" name="color[]" id="color_0" /><input type="button" name="pick[]" onclick="setColor('color_0');" value="Pick" /></td>
              </tr>
            <tr>
             <td colspan="3"><input type="button" onclick="new_field();" value="Add Row" /></td>
            </tr>
           </table>
          </form>

          </body>
          </html>

          Hast du oder sonst jemand vielleicht noch eine Idee?
          Gruss
          Kane

          1. Hallo NetizenKane,

            input.rowcount = rowcount-1;
            input.onclick = function () { setColor('color_' + (rowcount-1) + ''); }

            Hier solltest du auf die dem Element als Eigenschaft zugewiesene, Variable rowcount mit dem zum Zeitpunkt seiner Erstellung um eins verminderten Wert verweisen, also:

              
            input.onclick = function () { setColor('color_' + this.rowcount + ''); }  
            
            

            function setColor(id)
            {
            document.getElementById(id).value = "Farbe";
            }

            Wenn du der Funktion statt einer ID gleich das Objekt übergibst, kann es dessen Value auch ohne den Umweg über getElementById ändern.

              
            input.meinAnzeigeFeld = document.createElement('input');  
            
            

            und später dann

              
            cell_2.appendChild(input);  
            cell_2.appendChild(input.meinAnzeigeFeld);  
            
            

            Dann kennt der Button (input) sein Anzeigefeld (input.meinAnzeigeFeld) und onclick kann er dann dieses, sein Anzeigefeld und nichts anderes in seiner jeweiligen Methode über diese seine Eigenschaft (this.meinAnzeigeFeld) ansprechen.

              
            input.onclick = function setValue () {  
                this.meinAnzeigeFeld.value = "Hier bin ich richtig";  
            }
            

            Gruß Gernot

            1. Hallo Gernot,

              deine Lösung liest sich gut (ich hab sie ehrlicherweise nicht ausprobiert). Ein Bekannter hatte eine einfachere Lösung durch Modifizierung meines ersten Skriptes:

              var pickColor_string = "pickColor('color_' + (rowcount-1) +'', 'colorwatch_' + (rowcount-1) +'');";

              wurde zu

              var pickColor_string = "pickColor('color_" + rowcount +"', 'colorwatch_" + rowcount +"');";

              Vielen Dank aber auf jeden Fall für deine Mühen.
              Viele Grüsse
              KAne