passing variables to a function with onclick in a forEach loop - Google Apps Script

maxgotstuck :

I'm trying to build a web app that gathers the content for a table from a spreadsheet. Therefor I put the content of the spreadsheet in an array and build the table in JavaScript with a forEach Loop. That works all fine, but I want one cell at each row to be clickable. When the cell is clicked I want to call another javascript function and pass the content of the other cells in the respective row as variables. I did it with adding a link in the cell that calls the function with the onclick event. When I click the cell then I see in the console logs, that the function is called but that the variables I wanted to pass are undefined.

I think the problem is that, when the cell is clicked the loop is over and the variable not defined anymore. But I totally don't know a workaround for this issue.

My HTML file with the javascript code (I'm talking about the else in section //Table JS, pretty much at the end of the code):

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">

    <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">

     <style>
      table.centered {
        table-layout: fixed;
        width: 100%;
      }
      td {
       width: 25%;
      }
      div.add-form{
       width: 50%;
      }
    </style>

  </head>
  <body>
    <h1>Buch hinzufügen</h1>
    <div class="add-form">
      <label>Buchtitel: </label><input type="text" id="titel" class="eingabefeld"> <br>
      <label>Buchautor: </label><input type="text" id="autor" class="eingabefeld"> <br>
      <label>Auflage: </label><input type="number" id="auflage" class="eingabefeld"> <br>
      <button id="btn">Hinzufügen</button>
    </div>

    <!-- Tabelle Buchliste -->
     <h1>Buchliste</h1>

        <table class="centered">
        <thead>
          <tr bgcolor="#f0f0f0">
              <th>Buchtitel</th>
              <th>Buchautor</th>
              <th>Auflage</th>
              <th>Status</th>
          </tr>
        </thead>

        <tbody id="table-body">

        </tbody>
      </table>

    <script>

    //Buch hinzufügen JS
    document.getElementById("btn").addEventListener("click",doStuff);

    function doStuff(){
      var titel =  document.getElementById("titel").value;
      var autor =  document.getElementById("autor").value;
      var auflage =  document.getElementById("auflage").value; 

      //validateForm
      if (titel == "" || autor=="" || auflage=="") {
        alert("FEHLER: Alle Felder müssen ausgefüllt sein!");
        return false;
      }

      //passInfoToGoogleScript
      google.script.run.addInfo(titel, autor, auflage);

      //clearInputFields
      document.getElementById("titel").value = "";
      document.getElementById("autor").value = "";
      document.getElementById("auflage").value = "";
    }


    //Table JS
    document.addEventListener("DOMContentLoaded", function(){
        google.script.run.withSuccessHandler(addElementsToTable).getTableData();
      });

      function addElementsToTable(data){
        var tbody = document.getElementById("table-body");

        data.forEach(function(r){
          var row = document.createElement("tr");
          var col1 = document.createElement("td");
          col1.textContent = r[0];
          var col2 = document.createElement("td");
          col2.textContent = r[1];
          var col3 = document.createElement("td");
          col3.textContent = r[2];
          var col4 = document.createElement("td");


          if(r[3]=="nicht verfügbar"){
            console.log("nicht verfügbar erkannt");
            col4.textContent = r[3] + " | Klick zum Zurückgeben";
            col4.setAttribute("style", "background-color: #E40046;color: #ffffff");
          }

          else{
           console.log("verfügbar erkannt");
           var link = document.createElement("a");
           link.setAttribute("onclick", "test(r[0],r[1],r[2])");
           link.textContent = r[3] + " | Klick zum Ausleihen";
           link.setAttribute("style", "color: #ffffff;");
           col4.setAttribute("style", "background-color: #00965E;");
           col4.appendChild(link);
          }

          row.appendChild(col1);
          row.appendChild(col2);
          row.appendChild(col3);
          row.appendChild(col4);
          tbody.appendChild(row);

         });
      }

    function test(titel,autor,auflage) {
        console.log("Test");
        console.log("Titel: " + titel + " | Autor: " + autor + " | Auflage: " + auflage);
      }
    </script>
  </body>
</html>

Error Message: "Uncaught ReferenceError: r is not defined at HTMLAnchorElement.onclick"

T.J. Crowder :

Don't use onxyz attributes for event handling in modern programs. The function they create is created at global scope. Your r variable is local to the the function call.

Instead, use modern event handling:

link.addEventListener("click", function() {
    test(r[0], r[1], r[2]);
});

When the click occurs, that will call test with the then-current values in r. The event handler closes over r.

Live Example:

var fakeData = [
    [
        "first row value 0",
        "first row value 1",
        "first row value 2",
        "first row value 3"
    ],
    [
        "second row value 0",
        "second row value 1",
        "second row value 2",
        "nicht verfügbar"
    ],
    [
        "third row value 0",
        "third row value 1",
        "third row value 2",
        "third row value 3"
    ]
];

function test(x, y, z) {
    console.log(x, y, z);
}

var tbody = document.getElementById("the-body");

fakeData.forEach(function(r) {
    var row = document.createElement("tr");
    var col1 = document.createElement("td");
    col1.textContent = r[0];
    var col2 = document.createElement("td");
    col2.textContent = r[1];
    var col3 = document.createElement("td");
    col3.textContent = r[2];
    var col4 = document.createElement("td");

    if(r[3]=="nicht verfügbar"){
        console.log("nicht verfügbar erkannt");
        col4.textContent = r[3] + " | Klick zum Zurückgeben";
        col4.setAttribute("style", "background-color: #E40046;color: #ffffff");
    }

    else{
        console.log("verfügbar erkannt");
        var link = document.createElement("a");
        link.addEventListener("click", function() {
            test(r[0], r[1], r[2]);
        });
        link.textContent = r[3] + " | Klick zum Ausleihen";
        link.setAttribute("style", "color: #ffffff;");
        col4.setAttribute("style", "background-color: #00965E;");
        col4.appendChild(link);
    }

    row.appendChild(col1);
    row.appendChild(col2);
    row.appendChild(col3);
    row.appendChild(col4);
    tbody.appendChild(row);

});

// Subsequent change to the underlying data
fakeData.forEach(function(r) {
    r[0] += " - updated";
    r[1] += " - updated";
    r[2] += " - updated";
});
<table><tbody id="the-body"></tbody></table>

Notice how when you click, it sees the updated values of r[0] etc.


If you want the function to use the current values of the values in r (as of when the function is created, not when it's clicked), you'd use bind instead:

link.addEventListener("click", test.bind(null, r[0], r[1], r[2]));

That grabs the values as of when you create the function.

Live Example:

var fakeData = [
    [
        "first row value 0",
        "first row value 1",
        "first row value 2",
        "first row value 3"
    ],
    [
        "second row value 0",
        "second row value 1",
        "second row value 2",
        "nicht verfügbar"
    ],
    [
        "third row value 0",
        "third row value 1",
        "third row value 2",
        "third row value 3"
    ]
];

function test(x, y, z) {
    console.log(x, y, z);
}

var tbody = document.getElementById("the-body");

fakeData.forEach(function(r) {
    var row = document.createElement("tr");
    var col1 = document.createElement("td");
    col1.textContent = r[0];
    var col2 = document.createElement("td");
    col2.textContent = r[1];
    var col3 = document.createElement("td");
    col3.textContent = r[2];
    var col4 = document.createElement("td");

    if(r[3]=="nicht verfügbar"){
        console.log("nicht verfügbar erkannt");
        col4.textContent = r[3] + " | Klick zum Zurückgeben";
        col4.setAttribute("style", "background-color: #E40046;color: #ffffff");
    }

    else{
        console.log("verfügbar erkannt");
        var link = document.createElement("a");
        link.addEventListener("click", test.bind(null, r[0], r[1], r[2]));
        link.textContent = r[3] + " | Klick zum Ausleihen";
        link.setAttribute("style", "color: #ffffff;");
        col4.setAttribute("style", "background-color: #00965E;");
        col4.appendChild(link);
    }

    row.appendChild(col1);
    row.appendChild(col2);
    row.appendChild(col3);
    row.appendChild(col4);
    tbody.appendChild(row);

});

// Subsequent change to the underlying data
fakeData.forEach(function(r) {
    r[0] += " - updated";
    r[1] += " - updated";
    r[2] += " - updated";
});
<table><tbody id="the-body"></tbody></table>

Notice how when you click, it sees the values of r[0] etc. as they were when the click handler was created.

I suspect you want the latter (with bind), but there are use cases for both.


Side note: Rather than setting the style attribute, you can work directly with its reflected property. For instance, this:

col4.setAttribute("style", "background-color: #E40046;color: #ffffff");

completely replaces the inline styles on the element (e.g., removing any that were there before). If you want that, great; otherwise, though, you can do:

col4.style["background-color"] = "#E40046"; // Notice the [""] syntax
col4.style.color = "#ffffff";

or

col4.style.backgroundColor = "#E40046"; // Notice no "-" and a capital C
col4.style.color = "#ffffff";

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=34428&siteId=1