Javascript deleteRow causing indexSizeError, need closure - javascript

I have this code:
var table = document.getElementById("editTable");
var row = table.insertRow(-1);
var i = row.rowIndex;
var remove = document.createElement("input");
remove.type = "button";
remove.value = "Remove";
remove.onclick = (function() {
var I = i;
return function() {
table.deleteRow(I);
}
})();
var td1 = row.insertCell(-1);
td1.appendChild(remove);
I have read several articles here and I don't understand what I am doing wrong. When I try to delete the last row that I create , I get this error:
IndexSizeError: Index or size is negative or greater than the allowed amount
table.deleteRow(I);
I am pretty sure this is a closure issue. I understand scope but not syntax for anonymous functions in javascript;

I think you're over thinking the whole function/anonymous function/closure stuff here. It's looking a little too complicated. Try this code:
var table = document.getElementById("editTable");
var row = table.insertRow(-1);
var remove = document.createElement("input");
//Append input first so you can get it's parent
var td1 = row.insertCell(-1)
.appendChild(remove);
remove.type = "button";
remove.value = "Remove";
remove.onclick = function () {
var parent = this.parentNode.parentNode; //get the row node
table.deleteRow(parent.rowIndex - 1); //Delete the row index behind it.
};
jsFiddle

Corey, I see you have a working solution but you may be interested in something closer to your original idea.
The problem with your original code appears to be that i becomes an unreliable measure of current row index after other rows have been removed. Trapping i in a closure is not a solution - you just trap a value that is guaranteed to be correct only at the time it is trapped.
However trapping row itself, then obtaining row.rowIndex when it's needed will be reliable, because row.rowIndex gives the current index, not the index at the time the row was appended to the table.
remove.onclick = (function(row) {
return function() {
table.deleteRow(row.rowIndex);
};
})(row);

here is the code working:
var remove = document.createElement("input");
remove.type = "button";
remove.value = "Remove";
remove.onclick = function () {
var parent = this.parentNode.parentNode; //get the row node
table.deleteRow(parent.rowIndex); //Delete the row index behind it.
};
var td1 = row.insertCell(-1)
.appendChild(remove);

Related

Element in the appended table is always duplicate when add new element to firebase-database

After adding a new element to the firebase-database, the appended HTML table's all elements have been duplicated.
I've tried to fix it using below code snippets. but it is not working.
var userlistings = document.querySelectorAll('userlisting')
for(var i = 0; i < userlistings.length; i++){
userlistings[i].remove();
}
and
createElementWithText('userlisting');
Here is the full code.
var dbRefUsers = firebase.database().ref().child('Web App').child('Users');
dbRefUsers.on('value', gotData, errData);
function gotData(data){
var userlistings = document.querySelectorAll('userlisting')
for(var i = 0; i < userlistings.length; i++){
userlistings[i].remove();
}
var users = data.val();
var keys = Object.keys(users)
for (var i = 0; i < keys.length; i++){
var k = keys[i];
var userName = users[k].Name;
var userEmail = users[k].Email;
var userPassword = users[k].Password;
console.log(userName, userEmail, userPassword);
var appendingTo = document.getElementById("userlist");
function createElementWithText(tag, text) {
var elm = document.createElement(tag);
elm.textContent = text;
return elm;
}
var tr = document.createElement('tr');
createElementWithText('userlisting');
tr.appendChild(createElementWithText('td', userName));
tr.appendChild(createElementWithText('td', userEmail));
tr.appendChild(createElementWithText('td', userPassword));
appendingTo.appendChild(tr);
}
}
Could anyone please help me to fix this problem?
You're listening for a value event on /Web App/Users:
var dbRefUsers = firebase.database().ref().child('Web App').child('Users');
dbRefUsers.on('value', gotData, errData);
When you attach this listener your gotData is called straight away with a snapshot of all data under /Web App/Users. And then whenever something changes under there, gotData is called again with a snapshot of the latest data. So this latest snapshot contains the original data and the changes.
There are two main ways to deal with this:
Clear the UI before rerendering it
Listen to child events and perform granular updates
Clear the UI before rerendering it
The simplest approach is to make your userlist element empty each time gotData is called with something like:
document.getElementById("userlist");
appendingTo.innerHTML = "";
This will work, but you end up rerendering mostly the same elements, which may cause some unnecessary flicker. Not surprisingly, there is a way to fix this by...
Listen to child events and perform granular updates
Since you listen for the value event, Firebase gives you a snapshot of all data under the location. You can also listen for events one level lower, in which case Firebase will tell you when for example a user has been added.
A good starting point for that is to listen for child_added:
var appendingTo = document.getElementById("userlist");
dbRefUsers.on('child_added', gotNewChild, errData);
function gotNewChild(userSnapshot){
var user = userSnapshot.val();
var userName = user.Name;
var userEmail = user.Email;
var userPassword = user.Password;
var tr = document.createElement('tr');
tr.id = userSnapshot.key
tr.appendChild(createElementWithText('td', userName));
tr.appendChild(createElementWithText('td', userEmail));
tr.appendChild(createElementWithText('td', userPassword));
appendingTo.appendChild(tr);
}
A few things to note here:
There is no loop inside gotNewChild, since it gets called for each individual child/user node.
I set tr.id = snapshot.key, so that you can later look up the tr for a user by its key with var userElm = document.getElemenyById(userSnapshot.key)
This code won't work:
var userlistings = document.querySelectorAll('userlisting')
for(var i = 0; i < userlistings.length; i++){
userlistings[i].remove();
}
I removed the (need for this) code, but I highly recommend that you learn to troubleshoot this type of code yourself. I recommend setting a breakpoint on the for line and running the code in a debugger. If you inspect userlistings, you will see that it contains nothing. This is because you don't do anything with createElementWithText('userlisting'). If you're looking to remove the tr elements, you need to select them with document.querySelectorAll('#userlist > tr').

Dynamically generated buttons passing variable value in onclick event to deeper nested functions

I'm working on creating a save dialog for a browser based game and I'm having a bit of an issue. I've got saving and listing the saves down, but I'm having an issue with loading, overwriting, and deleting. I've got the save entries created dynamically, along with the buttons for manipulating each save. Clicking any of the buttons will open a confirmation dialog, upon which clicking yes will execute the actual action. My issue lies with getting each operation to actually function on the relevant save. The code I have so far looks something like this:
var saveNum, buttonType;
function loadSaveList(){
saves = JSON.parse(localStorage.getItem("game_saves"));
for(var a=0; a < saves.length; a++){
var entry = document.createElement("div");
var buttons = document.createElement("div");
var overwriteB = document.createElement("input");
var deleteB = document.createElement("input");
var loadB = document.createElement("input");
overwriteB.type = "button";
deleteB.type = "button";
loadB.type = "button";
overwriteB.onclick = function(){buttonType = 0; saveNum = function(){return a;}; saveAlert();};
deleteB.onclick = function(){buttonType = 1; saveNum = function(){return a;}; saveAlert();};
loadB.onclick = function(){buttonType = 2; saveNum = function(){return a;}; saveAlert();};
buttons.appendChild(overwriteB);
buttons.appendChild(deleteB);
buttons.appendChild(loadB);
entry.appendChild(buttons);
document.getElementById("saveEntries").appendChild(entry);}}
function saveAlert(){
switch(buttonType){
case 0:
document.getElementById("alertYes").onclick = overwriteButton;
document.getElementById("alertNo").onclick = function(){/*styling to close alert*/}; break;
case 1:
document.getElementById("alertYes").onclick = deleteButton;
document.getElementById("alertNo").onclick = function(){/*styling to close alert*/}; break;
case 2:
document.getElementById("alertYes").onclick = loadButton;
document.getElementById("alertNo").onclick = function(){/*styling to close alert*/}; break;}}
function overwriteButton(){
unloadSaveList();
saves.splice(saveNum, 1, currentSave);
localStorage.setItem("game_saves", JSON.stringify(saves));
loadSaveList();}
function deleteButton(){
unloadSaveList();
saves.splice(saveNum, 1);
localStorage.setItem("game_saves", JSON.stringify(saves));
loadSaveList();}
function loadButton(){
var tempSave = saves.slice(saveNum, saveNum+1);
currentSaveLoad(tempSave);
unloadSaveList();
saves = [];}
My intention was to use saveNum to store the value of a so the bottom 3 functions know which save to operate on. I figured a closure was the way to go to get the value all the way through, but I'm honestly not quite sure how to implement it in a way that would work. I'm looking primarily for a solution in vanilla js, but failing that, an alternate way of approaching the problem would be welcome.
Man, it turns out I was so close. I made 2 small changes:
I changed var to let in the for loop constructor:
for(let a=0; a < saves.length; a++){
And I changed saveNum to accept the value of a itself:
loadB.onclick = function(){buttonType = 2; saveNum = a; saveAlert();};
Thanks to Hunter Frazier for pointing me in the right direction.

Swapping html table elements (the complete object) in javascript

EDIT: I just noticed that I'm trying to swap integers because I'm getting the rowIndex. That's not what I want to do. I want to swap objects. However, I don't really know how.
I want to move table elements up and down in a queue by swapping with the element above and the element below respectively. However, my code doesn't work. The following is the JS function for the downButton.
var downButton = document.getElementsByClassName('downButton')[0];
downButton.onclick = function moveDown(currentRow) {
var index = currentRow.parentNode.parentNode.rowIndex;
var nextRow = currentRow.nextElementSibling.parentNode.parentNode.rowIndex;
var temp = index;
index = nextRow;
nextRow = temp;
}
This is the html for the down button:
<button class = "downButton">down</button>
Here's a JSFiddle link for the table: https://jsfiddle.net/Led4b3nw/
Swap innerHTML instead of indexes:
var index = currentRow.parentNode.parentNode;
var nextRow = currentRow.nextElementSibling.parentNode.parentNode;
var temp = index.innerHTML;
index.innerHTML = nextRow.innerHTML;
nextRow.innerHTML = temp;

join an array of elements to append it to a node

How can I do something like:
var html = [];
var tr = document.createElement('tr');
var tr_two = document.createElement('tr');
var tr_three = document.createElement('tr');
html.push(tr);
html.push(tr_two);
html.push(tr_three);
html.join(''); //This doesnt work
document.getElementById('tbody').appendChild(html);
So I need to join my array in a way that is doable, so I hit the DOM once.
Try:
var tbody = document.getElementById('tbody');
html.forEach(funtion(node) {
tbody.appendChild(node);
});
You are trying to join DOM elements instead strings.
You can use outerHTML to achive what you want.
var html = [];
var tr = document.createElement('tr');
var tr_two = document.createElement('tr');
var tr_three = document.createElement('tr');
html.push(tr.outerHTML);
html.push(tr_two.outerHTML);
html.push(tr_three.outerHTML);
html.join(''); //Now it will work
And to append to DOM use
document.getElementById('tbody').innerHTML += html.join('');
The appendChild() function can only be used with nodes, not strings. To do that with your current code, change it to the following:
var html = [];
var tr = document.createElement('tr').appendChild(document.createTextNode("tr 1"));
var tr_two = document.createElement('tr').appendChild(document.createTextNode("tr 2"));
var tr_three = document.createElement('tr').appendChild(document.createTextNode("tr 3"));
html.push(tr);
html.push(tr_two);
html.push(tr_three);
var tbody = document.getElementById("tbody");
html.forEach(function(elem) {
tbody.appendChild(elem);
})
I've added text nodes to each of the trs created so you can see the difference between each one.
The forEach loop just iterates over each element in array you created, and appends each one at time to the element you specified.
Here's a JSFiddle.

Javascript - get all table -> tr values

<table>
<tr><td>foo</td></tr>
<tr><td>bar</td></tr>
<tr><td>abc#yahoo.com</td></tr>
</table>
Can anybody tell me how to write a Javascript line to only grab the email address in the table below, I've been searching a lot, but all I come across is tutorials which use "id" in either table on in td .. I want to do it without having an id .. please help
var rows = document.getElementsByTagName("table")[0].rows;
var last = rows[rows.length - 1];
var cell = last.cells[0];
var value = cell.innerHTML
Try it yourself here: http://jsfiddle.net/ReyNx/.
Obviously you'll have to change document.getElementsByTagName("table")[0] to appropriately match your table
If you're using jQuery it's easier:
var value = $('table tr:last td').text();
For more info, see the MDN DOM reference, which shows you which properties are available on which elements to traverse the DOM.
No jQuery, innerHtml or other evil / heavy functions, just plain old JavaScript:
// Get the first table in the document.
var table = document.getElementsByTagName('table')[0];
// Get the third row of this table (0-index 3rd = 2)
var emailRow = table.rows[2];
// Get this element's content.
var emailContent = emailRow.firstChild.textContent;
You could write it in 1 line:
var emailContent = document.getElementsByTagName('table')[0].rows[2].firstChild.textContent;
If you want to find all email addresses in a table:
var emails = [];
var table = document.getElementsByTagName('table')[0];
var rows = table.rows;
for (var i = 0; i < rows.length; i++) {
var rowText = rows[i].firstChild.textContent;
if (~rowText.indexOf('#')) { // If the content of the row contains a '#' character (This could be replaced with a regex check)
// Also, I personally prefer to use '~' over '> -1' for indexOf(), but both would work.
emails.push(rowText);
}
}
console.log(emails);
Working example
If like me you want to get the text from all the first column items in all the tables on the page then use this.
jQuery('table tr td').each( function( cmp ) {
console.log( jQuery(this).text() );
} );
I wanted to extract all emails, but I had more than 1,000 rows and 17 columns/cells.
I used vanilla js, made some adjustments to get my desired output
var table = document.getElementsByTagName("table")[0]; //first table
var rows = table.rows;
//loop through rows
for (var i = 0; i < rows.length; i+=1) {
var emailTr = rows[i];
var emailTd = emailTr.cells[2]; //target third column/cell
var email = emailTd.innerHTML; //get the value
console.log(email + ', ');
var node = document.createElement("span"); // create span element
node.innerHTML = email + ', '; // desired output
document.body.appendChild(node); // display to document body
}
Assuming you're using vanilla Javascript (no libraries such as jQuery), and that this is the only table on the page, you can use the following code to select the third tr in the table, then find out what the td element contains
var table = document.getElementsByTagName("table")[0];
var emailTr = table.rows[2];
var emailTd = emailTr.cells[0];
var email = emailTd.innerHTML;
jQuery would make this easier
var email = $("table").children("tr:eq(2)").children("td").html();
A simple way is to give it a common class. Try:
<table>
<tr><td class="email">foo</td></tr>
<tr><td class="email">bar</td></tr>
<tr><td class="email">abc#yahoo.com</td></tr>
</table>
<script>
function getEmail(){
var email = new Array();
var arr = document.getElementsByClassName('email');
for(var i=0; i< arr.length; i++){
email.push(arr[i].innerHTML);
}
alert(email.join(','));
}
</script>
Demo
This is a solution in case you are using or plan to use jQuery library.
Given the email is always in the third row and first column (like in your example) then you can do as follows:
email = $('table tr:nth-child(3) td:first-child').html();
See working demo
Get all the <tr> elements. Loop through each one and compare the innerHTML against a regex that matches email addresses.
var emailAddresses = [];
var cells = document.getElementsByTagName("td");
for (var i = 0; i < cells.length; i++) {
if (cells[i].innerHTML.match(/yourEmailRegex/g)) {
emailAddresses[emailAddresses.length] = cells[i].innerHTML;
}
}
Find the appropriate regular expression here http://www.regular-expressions.info/email.html
in my case i want fifth column value of last row
var rows = document.getElementsByTagName("tbody")[0].rows;
var last = rows[rows.length - 1];
var cell = last.cells[4];
console.log(cell.textContent);

Categories