Deleting table rows using this in JavaScript - javascript

I'm trying to build an event that would delete a row in my table.In every row I have delete button and applicable row should be deleted once button in this row is clicked. Is there a way to do it using 'this' property? I tried with calculating indexes for row and button but it was too confusing, I am looking for simpler code. Here is what I got till now, part with 'this' doesn't work obviously. Can you advise if there is similar way to select applicable row?
document.addEventListener("DOMContentLoaded", function () {
var buttons = document.querySelectorAll(".deleteBtn");
function removeItem (e) {
var rows = document.querySelectorAll("tr");
rows[this].parentNode.removeChild(rows[this]);
}
for (var i = 0; i < buttons.length; i++) {
buttons[i].addEventListener("click", removeItem);
}
})

this will refer to your .deleteBtn element. Assuming that's inside the row you want to remove, you need to traverse up that element's parents to find the tr, and then remove it:
function removeItem(e) {
var tr = this;
while (tr.tagName != "TR") {
tr = tr.parentNode;
if (!tr) {
// Didn't find an ancestor TR
return;
}
}
tr.parentNode.removeChild(tr);
}
On modern browsers you could change the removeChild line to just:
tr.remove();
...but I have to admit I don't know how well-supported that is (I'm looking at you, Microsoft).

Related

Loop through a table to find specific id's

I have a table that I'm trying to insert a paypal button in the last cell of the table which is blank. I'm not sure how many rows will be in the table and I have the id's hard coded now which works. The id's begin with el and a number for each row then _qryMyReservedSlots_Payment
['#el1_qryMyReservedSlots_Payment', '#el2_qryMyReservedSlots_Payment', '#el3_qryMyReservedSlots_Payment'].forEach(function(selector) {
paypal.Button.render({
...paypal code...
});
});
to be more efficient, how can I loop through the id's so I don't have to hard code them?
Scott
I'm not sure how many rows will be in the table and I have the id's
hard coded now which works
Use querySelectorAll and attribute contains selector - *
var allRows = document.querySelectorAll( "tr[id*='qryMyReservedSlots_Payment']");
Array.from( allRows ).forEach( function(rowElement){
//logic with rowElement
})
Use a for loop:
for (var el = 1; el < 4; el++) {
var selector = `#el${el}_qryMyReservedSlots_Payment`
...
}
or more old-fashioned:
for (var el = 1; el < 4; el++) {
var selector = '#el' + el + '_qryMyReservedSlots_Payment'
...
}
Do you need them to have unique ids ? You should give them a class and then do something like document.getElementsByClassName(className).forEach(...).
If you insist on having unique ids (which, again, is not needed and this is exactly one of the reasons why we have classes), your could would be something like this:
while (document.getElementById(`el${ counter++ }_qryMyReservedSlots_Payment`)) {
paypal.Button.render({
...paypal code...
});
}
Again, this is not good because each time you query the id, you are hitting the DOM. You should really get it all at once, manipulate in-memory, and then commit all your changes in as few DOM calls as possible.

Event listener fails to attach or remove in some contexts

I've created a script that attaches an event listener to a collection of pictures by default. When the elements are clicked, the listener swaps out for another event that changes the image source and pushes the id of the element to an array, and that reverses if you click on the swapped image (the source changes back and the last element in the array is removed). There is a button to "clear" all of the images by setting the default source and resetting the event listener, but it doesn't fire reliably and sometimes fires with a delay, causing only the last element in a series to be collected.
TL;DR: An event fires very unreliably for no discernible reason, and I'd love to know why this is happening and how I should fix it. The JSFiddle and published version are available below.
I've uploaded the current version here, and you can trip the error by selecting multiple tables, pressing "Cancel", and selecting those buttons again. Normally the error starts on the second or third pass.
I've also got a fiddle.
The layout will be a bit wacky on desktops and laptops since it was designed for phone screens, but you'll be able to see the issue and inspect the code so that shouldn't be a problem.
Code blocks:
Unset all the selected tables:
function tableClear() {
//alert(document.getElementsByClassName('eatPlace')[tableResEnum].src);
//numResTables = document.getElementsByClassName('eatPlace').src.length;
tableArrayLength = tableArray.length - 1;
for (tableResEnum = 0; tableResEnum <= tableArrayLength; tableResEnum += 1) {
tableSrces = tableArray[tableResEnum].src;
//alert(tableSrcTapped);
if (tableSrces === tableSrcTapped) {
tableArray[tableResEnum].removeEventListener('click', tableUntap);
tableArray[tableResEnum].addEventListener('click', tableTap);
tableArray[tableResEnum].src = window.location + 'resources/tableBase.svg';
} /*else if () {
}*/
}
resTableArray.splice(0, resTableArray.length);
}
Set/Unset a particular table:
tableUntap = function () {
$(this).unbind('click', tableUntap);
$(this).bind('click', tableTap);
this.setAttribute('src', 'resources/tableBase.svg');
resTableArray.shift(this);
};
tableTap = function () {
$(this).unbind('click', tableTap);
$(this).bind('click', tableUntap);
this.setAttribute('src', 'resources/tableTapped.svg');
resTableArray.push($(this).attr('id'));
};
Convert the elements within the 'eatPlace' class to an array:
$('.eatPlace').bind('click', tableTap);
tableList = document.getElementsByClassName('eatPlace');
tableArray = Array.prototype.slice.call(tableList);
Table instantiation:
for (tableEnum = 1; tableEnum <= tableNum; tableEnum += 1) {
tableImg = document.createElement('IMG');
tableImg.setAttribute('src', 'resources/tableBase.svg');
tableImg.setAttribute('id', 'table' + tableEnum);
tableImg.setAttribute('class', 'eatPlace');
tableImg.setAttribute('width', '15%');
tableImg.setAttribute('height', '15%');
$('#tableBox').append(tableImg, tableEnum);
if (tableEnum % 4 === 0) {
$('#tableBox').append("\n");
}
if (tableEnum === tableNum) {
$('#tableBox').append("<div id='subbles' class='ajaxButton'>Next</div>");
$('#tableBox').append("<div id='cazzles' class='ajaxButton'>Cancel</div>");
}
}
First mistake is in tapping and untapping tables.
When you push a Table to your array, your pushing its ID.
resTableArray.push($(this).attr('id'));
It will add id's of elements, depending on the order of user clicking the tables.
While untapping its always removing the first table.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift
resTableArray.shift(this);
So, when user clicks tables 1, 2, 3. And unclicks 3, the shift will remove table 1.
Lets fix this by removing untapped table
tableUntap = function () {
$(this).unbind('click', tableUntap);
$(this).bind('click', tableTap);
this.setAttribute('src', 'http://imgur.com/a7J8OJ5.png');
var elementID = $(this).attr('id');
var elementIndex = resTableArray.indexOf(elementID);
resTableArray.splice(elementIndex, 1);
};
So you were missing some tables after untapping.
Well lets fix tableClear,
You have a array with tapped tables, but you are searching in main array.
function tableClear() {
tableLen = resTableArray.length;
for (var i = 0; i < tableLen; i++) {
var idString = "#" + resTableArray[i];
var $element = $(idString);
$element.unbind('click', tableUntap);
$element.bind('click', tableTap);
$element.attr("src", 'http://imgur.com/a7J8OJ5.png');
}
resTableArray = [];
}
Im searching only tapped tables, and then just untap them and remove handlers.
fiddle: http://jsfiddle.net/r9ewnxzs/
Your mistake was to wrongly remove at untapping elements.

Weird behaviour by node.removeChild(child)

I have created a very basic layout of tables and checkboxes. I have eight textboxes and eight rows in a table. I just tried to add row on checkbox checking and remove on unchecking.
So, I am using two functions for the same.
function show(input){
var tbody = document.getElementById("tbody");
if(document.contains(document.getElementById("tr"+input)))
{
hide('tr'+input);
}
if(!document.contains(document.getElementById("tr"+input)))
{
tbody.appendChild(getRow(input));
}
}
function hide(input){
if(document.contains(document.getElementById(input)))
{
var child = document.getElementById(input);
child.parentNode.removeChild(child);
child.parentNode.removeChild(child);
}
}
In the hide function, if I use only one removeChild statement, it does not work. On using two, an error is reported in the console but it works perfectly.
If anyone knows the reason, please tell me because it is not ethical to leave an error with the code.
Edit #1: JsFiddle
Your problem is this function:
function show(input) {
var tbody = document.getElementById("tbody");
if (document.contains(document.getElementById("tr" + input))) {
hide('tr' + input);
}
if (!document.contains(document.getElementById("tr" + input))) {
tbody.appendChild(getRow(input));
}
}
First, you check if the node is present and, if so, hide it. Next, you always check if the node is not present and, if so, you add it. When the node was just hidden, the second check will be true (because you just deleted the node) and the node is added back again.
So rewrite to this:
function show(input) {
var tbody = document.getElementById("tbody");
if (document.contains(document.getElementById("tr" + input))) {
hide('tr' + input);
} else if (!document.contains(document.getElementById("tr" + input))) {
tbody.appendChild(getRow(input));
}
}
fiddle
Seems there is a problem with immediate repaint of the element, here is a dirty way that worked for me
var child = document.getElementById(input);
setTimeout(function(){
child.parentNode.deleteRow(child.rowIndex - 1);
}, 1);
And it's safe to use table specific methods while appending/deleting table elements.

Attaching change() even listeners to select/dropdown dynamically

I'm creating 3 dropdowns/select boxes on the fly and insert them in the DOM through .innerHTML.
I don't know the ID's of the dropdowns until I created them in Javascript.
To know which dropdowns have been created, I create an array where I store the ID's of the dropdowns I have created.
for(var i=0; i<course.books.length; i++)
{
output+="<label for='book_"+course.books[i].id+"'>"+ course.books[i].name +"</label>";
output+="<select id='variant"+course.books[i].id+"' name='book_"+course.books[i].id+"'>";
output+="<option value='-'>-- Select one --</option>";
for(var j=0; j<course.books[i].options.length; j++)
{
output+="<option value='"+course.books[i].options[j].id+"'>"+course.books[i].options[j].name+"</option>";
}
output+="</select>";
}
Now I have an array with 3 id's like:
dropdown1
dropdown2
dropdown3
What I want to accomplish with Javascript (without using jQuery or another framework) is to loop over these 3 dropdowns and attach a change event listener to them.
When a user changes the selection in one of these dropdown, I want to call a function called updatePrice for example.
I'm a bit stuck on the dynamic adding of event listeners here.
Now you have added your code its straight forward and you can ignore my verbose answer !!!
output+="<select id='variant"+course.books[i].id+"' name='book_"+course.books[i].id+"'>";
could become :
output+="<select onchange="updatePrice(this)" id='variant"+course.books[i].id+"' name='book_"+course.books[i].id+"'>";
This will call the updatePrice function, passing the select list that changed
However
IMO its far better (from a performance point of view for a start) to create elements in the DOM using the DOM.
var newSelect = document.createElement("select");
newSelect.id = "selectlistid"; //add some attributes
newSelect.onchange = somethingChanged; // call the somethingChanged function when a change is made
newSelect[newSelect.length] = new Option("One", "1", false, false); // add new option
document.getElementById('myDiv').appendChild(newSelect); // myDiv is the container to hold the select list
Working example here -> http://jsfiddle.net/MStgq/2/
You got the array already? Then you can do this:
function updatePrice()
{
alert(this.id + " - " + this.selectedIndex);
}
var list = ["dropdown1", "dropdown2"];
for(var i=0;i<list.length;i++)
{
document.getElementById(list[i]).onchange = updatePrice;
}
Fiddle: http://jsfiddle.net/QkLMT/3/
That won't work across browsers.
You'll want something like
for(var i = 0; i < list.length; i++)
{
$("#"+list[i]).change(updatePrice);
}
in jquery.

Dynamic index setting in javascript

I have a table to which i need to add rows dynamically on click of a button. Each row has 3 textboxes and a clear button. On click of clear the data in the textboxes need to be cleared i.e. onclick of the button i send the index of the row to a method which deletes the contents of the textboxes at that index.
Problem - How do i specify the index number in the onClick property of the row button while adding the new row?
How do i specify the index number in the onClick property of the row button while adding the new row?
You don't. Instead, use the fact that the textboxes and the button are in the same row. I probably wouldn't use onclick on the button at all; instead, I'd have a single click handler on the table and handle the button clicks there (this is called event delegation). Something like this:
var table = document.getElementById("theTableID");
table.onclick = function(event) {
var elm, row, boxes, index, box;
// Handle IE difference
event = event || window.event;
// Get the element that was actually clicked (again handling
// IE difference)
elm = event.target || event.srcElement;
// Is it my button?
if (elm.name === "clear") {
// Yes, find the row
while (elm && elm !== table) {
if (elm.tagName.toUpperCase() === "TR") {
// Found it
row = elm;
break;
}
elm = elm.parentNode;
}
if (row) {
// Get all input boxes anywhere in the row
boxes = row.getElementsByTagName("input");
for (index = 0; index < boxes.length; ++index) {
box = boxes[index];
if (box.name === "whatever") {
box.value = "";
}
}
}
}
};
...but if you want to keep using the onclick attribute on the button instead, you can grab the middle of that:
The button:
<input type="button" onclick="clearBoxes(this);" ...>
The function:
function clearBoxes(elm) {
var row, boxes, index, box;
// Find the row
while (elm) {
if (elm.tagName.toUpperCase() === "TR") {
// Found it
row = elm;
break;
}
elm = elm.parentNode;
}
if (row) {
// Get all input boxes anywhere in the row
boxes = row.getElementsByTagName("input");
for (index = 0; index < boxes.length; ++index) {
box = boxes[index];
if (box.name === "whatever") {
box.value = "";
}
}
}
}
References:
DOM2 Core specification - well-supported by all major browsers
DOM2 HTML specification - bindings between the DOM and HTML
DOM3 Core specification - some updates, not all supported by all major browsers
HTML5 specification - which now has the DOM/HTML bindings in it, such as for HTMLInputElement so you know about the value and name properties.
Off-topic: As you can see, I've had to work around some browser differences and do some simple utility things (like finding the nearest parent element of an element) explicitly in that code. If you use a decent JavaScript library like jQuery, Prototype, YUI, Closure, or any of several others, they'll do those things for you, letting you concentrate on your actual problem.
To give you an idea, here's that first example (handling the click via event delegation) written with jQuery:
$("#theTableID").delegate("input:button[name='clear']", "click", function() {
$(this).closest("tr").find("input:text[name='whatever']").val("");
});
Yes, really. And other libraries will similarly make things simpler.
Best to use event delegation, or you can use this in JavaScript.
Event Delegation w/jQuery
<input class="clear-row-btn" type="button" >Clear Row</input>
.live event
$(".clear-row-btn").live("click", function(){
var $tr = $(this).closest("tr");
$tr.find("input[type='text']").val("");
});
HTML w/onclick method
<input type="button" onclick="clearRow(this)" >Clear Row</input>
jQuery
function clearRow(btn) {
var $tr = $(btn).closest("tr");
$tr.find("input[type='text']").val("");
}
JavaScript
function clearRow(element) {
while(element.nodeName!='TR'){
element = element.parentNode;
}
//find textboxes inside the element, which is now the parent <tr>
}

Categories