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"
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";