I have a page where the user inputs a color and I call the onClick method to change the color of the individual cells of the table. However, when I click any cell, only the last cell (cell3 in this case) will change color. What am I doing wrong?
I get the error:
Message: 'document.getElementById(...)' is null or not an object
Line: 24
Char: 4
Code: 0
My code is:
<html>
<body>
<input type='text' id='userInput' value='yellow' />
<table border="1">
<tr>
<td id="1">cell1
</td>
</tr>
<tr>
<td id="2">cell2
</td>
</tr>
<tr>
<td id="3">cell3
</td>
</tr>
</table>
<script type="text/javascript">
for(var i = 1; i <= 3; i++){
document.getElementById(i).onclick = function(){
var newColor = document.getElementById('userInput').value;
document.getElementById(i).style.backgroundColor = newColor;
}
}
</script>
</body>
</html>
Change your HTML to this: An ID must start with an an alpha character. It is not valid to start with a number.
<table border="1">
<tr>
<td id="td1">cell1
</td>
</tr>
<tr>
<td id="td2">cell2
</td>
</tr>
<tr>
<td id="td3">cell3
</td>
</tr>
</table>
This is a very common Javascript issue: All the code shares the value of i which is 3 at the end of the loop. You can solve it by using another helper function like this:
function changeIt(i) {
// This inner function now has its own private copy of 'i'
return function() {
var newColor = document.getElementById('userInput').value;
document.getElementById("td" + i).style.backgroundColor = newColor;
}
}
for(var i = 1; i <= 3; i++){
document.getElementById(i).onclick = changeIt(i);
}
It can also be written using an anonymous function, but those are harder to read.
First of all, your for loop is wrong. Try:
for(var i = 1; i <= 3; i++) {
//code
}
Second, instead of retrieving the element each time in your loop, you could use this:
this.style.backgroundColor = document.getElementById('userInput').value;
Jeremy's answer is close but there is still a problem in that changeIt is not being called until the element is clicked, by which time the value of i is still three. Using Jeremy's update to the HTML the correct script can be written as...
function createChangeColorHandler(n) {
return function() {
var newColor = document.getElementById('userInput').value;
document.getElementById("td" + n).style.backgroundColor = newColor;
}
}
for(var i = 1; i <= 3; i++) {
// We pass i to the function createChangeColorHandler by value
// at the time of this pass of the loop rather than referencing
// the variable directly after the loop has finished
document.getElementById(i).onclick = createChangeColorHandler(i);
}
As an anonymous function...
for(var i = 1; i <= 3; i++) {
// We pass i to the function createChangeColorHandler by value
// at the time of this pass of the loop rather than referencing
// the variable directly after the loop has finished
document.getElementById(i).onclick = (function(n) {
return function() {
var newColor = document.getElementById('userInput').value;
document.getElementById("td" + n).style.backgroundColor = newColor;
}
})(i);
}
EDIT Jeremy's answer is now correct
Related
Update: Put in more context.
Update 2: The sum isn't adding correctly now, and the initial sum doesn't appear until I edit one of the values.
I am trying to add contentEditable items from a table in JavaScript.
The three rows I'd like to add are named "string1", "string2", and "string3", and "total" is the id of the row that displays the sum of all three strings.
I attempted to use parseInt to add the numerical values of the numbers already in the table, but the total sum is incorrect for some reason. Also, the the sum of the initial set of numbers is blank until I one of the values. How do I get it to appear when I open the file?
Below is the code:
<body>
<table>
<tr>
<td>First:</td>
<td id="string1" oninput="myFunction()">100</td>
</tr>
<tr>
<td>Second:</td>
<td id="string2" oninput="myFunction()">200</td>
</tr>
<tr>
<td>Third:</td>
<td id="string3" oninput="myFunction()">100</td>
</tr>
<tr>
<td>Sum</td>
<td id="total"></td>
</tr>
</table>
</body>
<script type="text/javascript">
document.getElementById("string1").contentEditable = true;
document.getElementById("string2").contentEditable = true;
document.getElementById("string3").contentEditable = true;
var integer1 = document.getElementById("string1").innerText;
var integer2 = document.getElementById("string2").innerText;
var integer3 = document.getElementById("string3").innerText;
var sum = parseInt(integer1) + parseInt(integer2) + parseInt(integer3);
function myFunction() {
document.getElementById("total").innerHTML = sum;
}
</script>
What am I doing wrong?
Its here:
function myFunction() {
document.getElementById("total").innerHTML = sum;
}
Thats an declaration of an function, you just declare it.
You also need to execute that function to make it work with:
myFunction()
Your code looks like this then:
<script type="text/javascript">
document.getElementById("string1").contentEditable = true;
document.getElementById("string2").contentEditable = true;
document.getElementById("string3").contentEditable = true;
function myFunction() {
var integer1 = document.getElementById("string1").innerText;
var integer2 = document.getElementById("string2").innerText;
var integer3 = document.getElementById("string3").innerText;
var sum = parseInt(integer1) + parseInt(integer2) + parseInt(integer3);
}
myFunction();
</script>
You're not calling myFunction() so that code never gets executed.
To make it calculate whatever is in the elements string1, string2, string3 you need to listen for a change and then calculate the sum:
document.getElementById("string1").contentEditable = true;
document.getElementById("string2").contentEditable = true;
document.getElementById("string3").contentEditable = true;
function myFunction() {
// Table cells do not have .value property so we use .innerText
var integer1 = document.getElementById("string1").innerText;
var integer2 = document.getElementById("string2").innerText;
var integer3 = document.getElementById("string3").innerText;
var sum = parseInt(integer1, 10) + parseInt(integer2, 10) + parseInt(integer3, 10);
document.getElementById("total").innerHTML = isNaN(sum) ? 0 : sum;
}
document.getElementById("string1").addEventListener("input", myFunction);
document.getElementById("string2").addEventListener("input", myFunction);
document.getElementById("string3").addEventListener("input", myFunction);
// do initial calculation
myFunction();
Also you should always pass the second parameter (radix) of parseInt.
I'm on a Javascript class (still pretty new to the language) and trying to get my head around this problem. Basically I need to add an event listener which changes a string in a <td> row to something else when clicked on.
Now in theory what I've written below should work (I think) but for some reason k[i] returns an undefined value. Below is within a new function which triggers on page load:
var k = document.getElementsByTagName("td");
for( var i = 0; i < k.length; i++ ){
k[i].addEventListener("click", function(evt) {
k[i].textContent = "Success!";
});
}
If someone could help me get my head around where I'm going wrong I'd be really grateful!
evt.target.textContent = "Success!";
Instead of :
k[i].textContent = "Success!";
Good Luck ! Demo is below 👇🏻
var k = document.getElementsByTagName("td");
for( var i = 0; i < k.length; i++ ){
k[i].addEventListener("click", function(evt) {
evt.target.textContent = "Success!"; // ⚠️ So you have to do this
});
}
<table style="width:100%">
<tr>
<th>Firstname</th>
<th>Lastname</th>
<th>Age</th>
</tr>
<tr>
<td>Jill</td>
<td>Smith</td>
<td>50</td>
</tr>
<tr>
<td>Eve</td>
<td>Jackson</td>
<td>94</td>
</tr>
</table>
As Tomas said, the issue is with your variable scope, as an alternative to his answer you can use this keyword.
Here's a working fiddle.
https://jsfiddle.net/d9pzyf15/1/
HTML
<table>
<tr>
<td class="thing"> 1 </td>
<td class="thing"> 2 </td>
</tr>
</table>
JS
let tds = document.getElementsByTagName('td')
for(let i = 0; i < tds.length; i++){
tds[i].addEventListener('click', function(ev) {
this.textContent = 'Changed'
});
}
EDIT
As suggested by Abdennour in the comments, this would be the answer using ES6 arrow functions
let tds = document.getElementsByTagName('td')
for(let i = 0; i < tds.length; i++){
tds[i].addEventListener('click', (ev) => {
ev.target.textContent = 'Changed'
});
}
The problem is variable scope. When event listener is called i is different from time when event listener was created so k[i] contains undefined variable. Solution is written by Abdennour TOUMI
The problem as mentioned is scope.
You can use the target like #abdennour mentions, but sometimes these might not be available.
The easiest way to capture scope if using esnext stuff, is use let to capture the var,..
Another way if using es5, is use a self invoking anonymous function to capture..
Below are the three examples, the esNext one's is commented out. The second esnext one uses the the new for of, construct.. please note, not for in..
var k = document.getElementsByTagName("td");
for( var i = 0; i < k.length; i++ ){
(function (e) {
e.addEventListener("click", function(evt) {
e.textContent = "Success!";
});
})(k[i]);
}
/*or use esnext stuff*/
/*for( var i = 0; i < k.length; i++ ){
let e = k[i];
e.addEventListener("click", function(evt) {
e.textContent = "Success!";
});
}*/
/*or maybe use the for of.. note not for in*/
/*
for (let e of document.getElementsByTagName("td")) {
e.addEventListener("click", function(evt) {
e.textContent = "Success!";
});
}*/
<table>
<tbody>
<tr>
<td>one</td>
<td>two</td>
<td>three</td>
</tr>
</tbody>
</table>
I have made a custom chrome extension - a bookmark manager. It's working fine, but I am having problems with deleting the links: If 3 links are given, and delete button is clicked on the 2nd one, both 2 and 3 will be removed.
html + js here: http://jsfiddle.net/6Txr9/
Since this is an extension, inline javascript is not available, and it has to be done with an event listener.
the counter and linkContainer are saved locally. This allows for data to persist between ext launches and ensures that all #id are going to be unique. The idea is that indexDeleters() will add a unique eventlistener to each button each time it's called, which will cause it to delete only that row.
Any input is appreciated!
Code as requested (same as in link above):
js
function addCats()
{
var linkCounter = localStorage['counter']
var catsList = document.getElementById('catsList');
var linkName = document.getElementById('linkName');
var a = document.createElement('a');
var linkContainer = document.getElementById('linkContainer');
a.appendChild(document.createTextNode(document.getElementById('linkName').value));
a.setAttribute('href', 'http://google.com/');
a.setAttribute('target', '_blank');
var deleteLink = document.createElement('img');
deleteLink.setAttribute('src', 'red_x.png');
deleteLink.setAttribute('align', 'right');
deleteLink.setAttribute('id', linkCounter);
var tr = document.createElement('tr');
var linkCell = document.createElement('td');
var xCell = document.createElement('td');
linkCell.appendChild(a);
xCell.appendChild(deleteLink);
xCell.setAttribute('align', 'right');
tr.appendChild(linkCell);
tr.appendChild(xCell);
linkContainer.appendChild(tr);
catsList.value = '';
linkName.value = '';
localStorage['container'] = JSON.stringify(linkContainer.innerHTML);
indexDeleters();
linkCounter ++;
localStorage['counter'] = linkCounter;
}
document.getElementById('addToList').onclick = addCats;
function indexDeleters()
{
var Xs = document.getElementsByTagName('img');
var arr = []
for (var i=0; i<Xs.length; i++)
{
arr.push(Xs[i]);
}
for (var i=0; i<arr.length; i++)
{
var Id = arr[i].getAttribute('id');
arr[i].addEventListener('click', function(){removeRow(Id);}, false);
}
}
function removeRow(Id)
{
console.log('Call to remove id at ' + Id)
var Table = document.getElementById('linkContainer');
var Tr = document.getElementById(Id).parentNode.parentNode
Tr.parentNode.removeChild(Tr);
localStorage['container'] = JSON.stringify(document.getElementById('linkContainer').innerHTML);
}
window.onload = function()
{
if (localStorage.getItem('counter') == null)
{
localStorage['counter'] = 0
}
document.getElementById('linkContainer').innerHTML = JSON.parse(localStorage['container']);
indexDeleters();
}
html
<body>
<h2 align="center">Bookmark Manager</h2>
<table>
<tr>
<td colspan="5">
<table id="linkContainer" width="100%"></table>
</td>
</tr>
<tr>
<td nowrap>Display Name:</td>
<td>
<input type="text" id="linkName"/>
</td>
<td nowrap>Cat IDs:</td>
<td>
<input type="text" id="catsList"/>
</td>
<td>
<button type="button" id="addToList">Add</button>
</td>
</tr>
</table>
<script src="popup.js"></script>
</body>
See JavaScript closure inside loops – simple practical example for an explanation of the problem.
In your case, you can directly access the DOM element in the event handler, so that it doesn't depend on any loop variable.
arr[i].addEventListener('click', function(){removeRow(this.id);}, false);
I am trying to get to the logic of the Tic Tac Toe game which I almost have made a logic, but I am stuck while pushing the Data to the array. Here is a fiddle that I have created.
http://jsfiddle.net/afzaal_ahmad_zeeshan/6bgjp/1/
Let me explain the whole thing to you!
I am trying to use the 9 td of the table as the 8 rows of the possible win. For that I have given some of the tds a className depending on their location in the table.
The HTML is simple
<div class="vicvacvoe">
<table>
<tr>
<td class="line1 line4 line7"></td>
<td class="line1 line5"></td>
<td class="line1 line6 line8"></td>
</tr>
<tr>
<td class="line2 line4"></td>
<td class="line2 line5 line7 line8"></td>
<td class="line2 line6"></td>
</tr>
<tr>
<td class="line3 line4 line8"></td>
<td class="line3 line5"></td>
<td class="line3 line6 line7"></td>
</tr>
</table>
</div>
Just a simple table with 9 tds, the CSS is not relative to this so leave it I guess.
jQuery for this also simple one. But I am not able to push the data to the Array.
var vic = $('.vicvacvoe table tr td');
var player = 1;
var tick = '✓';
var cross = 'X';
var user1 = [];
var user2 = [];
vic.click(function () {
var className = $(this).attr('class');
if (className != 'dead') {
// A new board place to write on...
// Now do the processes here...
if (player == 1) {
// First player!
var cArray = className.split(' ');
for (i = 0; i < cArray.length; i++) {
for (j = 0; j < user1.length; j++) {
// check for each class
if (user1[j] != cArray[i]) {
user1.push(cArray[i]);
}
}
}
} else {
/* code for second player, the same */
}
$(this).text('Works!');
$(this).attr('class', 'dead');
}
});
This is the entire jQuery script. Actually when I run the code, it really does go to the end of the stack (to the class attribute change script) and it locks the td for further process and it write Works! in the td too. But I am not able to get the classNames inside the Array for that user. I want to save the line number for each user and then check whether he has 3 spots filled or not. I need help with the Array part.
Thanks!
I prefer simplicity so you could use indexOf to check whether the class is already in the users' array like so:
if (player == 1) {
// First player!
var cArray = className.split(' ');
for (i = 0; i < cArray.length; i++) {
if(user1.indexOf(cArray[i]) == -1) {
user1.push(cArray[i]);
} else {
// css class is already in the array
}
}
}
Your issue is here:
for (j = 0; j < user1.length; j++) {
The only place you add to the user1 array is within this loop. Now the array is initially empty, so clearly this loop will never iterate as user1.length is always 0.
I think your intent with this bit of code was to check if the value was already in the array in which case I suggest using $.inArray.
I wrote an answer to this question: Fetch content of next td on checkbox click, the answer was accepted (as of writing this question).
The intent was to find the text-value of the table-cell following the current cell that contained the input checkbox; for the second row this works (in Chrome 18/WinXP), but in the first row the evaluation console.log(that.checked); evaluates to false (regardless, so far as I can see, of it being checked or not).
The supplied HTML:
<table>
<tr>
<td>
<input type=checkbox name=t>
</td>
<td width=25%>
FOOBAR
</td>
<td width=73%>
BAZ
</td>
</tr>
<tr>
<td>
<input type=checkbox name=t>
</td>
<td width=25%>
FOO
</td>
<td width=73%>
BAR
</td>
</tr>
</table>
And my JavaScript:
var c = [];
c = window.document.getElementsByTagName('input');
for (var i = 0; i < c.length; i++) {
var that = c[i];
if (that.type == 'checkbox') {
that.onchange = function() {
console.log(that.checked);
if (that.checked){
console.log(that.parentNode.nextElementSibling.firstChild.nodeValue.trim());
}
};
}
}
JS Fiddle demo.
Note that it seems to work reliably for the second row (and logs FOO to the console), but in the first row the console logs only false. Is there an obvious mistake I'm making?
You're actually running into an issue unrelated to checked. Your that variable is outside the scope of the event handler, and so it is always resolving to c[1]. You need to either wrap the thing in a closure (aka function () { ... }(); or just change that to this inside your event handler, like in this: http://jsfiddle.net/z88HH/3/
for (var i = 0; i < c.length; i++) {
var that = c[i];
if (that.type == 'checkbox') {
that.onchange = function() {
console.log(this.checked);
if (this.checked){
console.log(this.parentNode.nextElementSibling.firstChild.nodeValue.trim());
}
};
}
}
Isn't that always the last row e.g. try console.log(that, that.checked) , wrap it in a closure see your edited jsFiddle
for (var i = 0; i < c.length; i++) {
if (c[i].type == 'checkbox') {
c[i].onchange = function(){
var that = c[i];
return function() {
console.log(that, that.checked);
if (that.checked){
console.log(that.parentNode.nextElementSibling.firstChild.nodeValue.trim());
}
}}();
}
}