I'm really new to JavaScript but I can't find out why this program won't work.
I want when I click the dynamically created button which is situated in a cell in my dynamically created table to get the rowindex of the row in which the button is situated.
Thanks in advance - here is my code:
<html>
<head>
<script>
function whichrow(obj) {
var par = obj.parentNode;
while(par.nodeName.toLowerCase()! = 'tr') {
par = par.parentNode;
}
alert(par.rowIndex);
}
</script>
</head>
<body>
<script>
mybody = document.getElementsByTagName("body")[0];
mytable = document.createElement("table");
mytablebody = document.createElement("tbody");
for(var i = 0; i<5; i++) {
mycurrent_row = document.createElement("tr");
mycurrent_row.id = "row"+i;
for(var j = 0; j<4; j++) {
mycurrentcell = document.createElement("td");
currenttext = document.createTextNode("Row" + i + "Column" + j);
mycurrentcell.appendChild(currenttext);
mycurrent_row.appendChild(mycurrentcell);
}
mybutoncell = document.createElement("td");
but = document.createElement("button");
mybutoncell.appendChild(but);
mycurrent_row.appendChild(mybutoncell);
mytablebody.appendChild(mycurrent_row);
but.onClick = whichrow(this);
}
mytable.appendChild(mytablebody);
mybody.appendChild(mytable);
mytable.setAttribute("border", "2");
</script>
</body>
</html>
Ok, so a few points to note here.
In the javascript eventing system, the system calls your callback with its own event object containing different properties according to what happened.
So, here are the mistakes:
When you're assigning to the event handler, when you say but.onclick = whichrow(this) you're setting but.onclick to the result of whichrow, which is undefined since you're not returning anything anyway. It should be but.onclick = whichrow; which will call whichrow when the user clicks your button. The parameter passed is a MouseEvent object. The link I've supplied should serve as a good start to read up on what kind of properties are available to you.
I have to check, since I use el.addEventListeners a lot, but onclick needs to be in lower case, not camelCase like you've done.
Inside the event callback, this usually refers to the element that was clicked, so you should use that.
There is no rowIndex property.
Now, trying to find a solution to your problem. rowIndex can be gleaned by traversing the dom. I'm not sure what purpose this will serve since you're creating the DOM by hand anyway and know the rowIndex already, but if it were unknown, here's what I would do
function whichRow(e) {
// here this should be the button.
var curRow = this.parentElement.parentElement,
rowIndex = 0;
while(curRow) {
curRow = curRow.previousElementChild;
rowIndex++;
}
return rowIndex;
}
I'm writing this off the top of my head, but the point is to give the main idea. In the above snippet, I've taken the parent of the parent of the button, since here's the approximate markup of the button section:
<tr>
<td>
<button></button>
</td>
</tr>
so, the parentElement of the parentElement of the button element should give you the <tr>. Then we'll traverse backwards till we don't have any previous elements, counting as we go. Once the previous element is null, return the count.
The obj you are passing to whichrow() is a button, which I assume inside a TD. So your while loop will exit in its first iteration itself, resulting in par holding a TD - which does not have a property named rowIndex
Related
I'm trying to create a chrome extension, my problem is that when I try to place an event listener to each button in a class, only the first button has one, and the rest don't have an event listener.
function copyButtonInitialise(){
var copyButtons = document.getElementsByClassName("copyPassword");
console.log("length = ", copyButtons.length);
for (var i = 0; i < copyButtons.length; i++){
console.log(copyButtons[i] + " element number " + i + "= button");
copyButtons[i].addEventListener("click", copyButtonClick);
}
}
This function is what should be called if any button with the class "copyPasword" is clicked.(just want to make sure it gets clicked, but it doesn't)
function copyButtonClick(){
console.log("Hello There");
}
This is the function that loads passwords, it's called before adding event listeners to buttons.
async function loadPasswords(){
document.getElementById("passwordTable").innerHTML = "";
console.log("This is the loadpasswords function");
chrome.storage.sync.get(null, function(items) {
var allKeys = Object.keys(items);
var passwordTable = document.getElementById("passwordTable");
var header = passwordTable.createTHead();
var passwordRow = header.insertRow(0);
for(var i = 0; i < allKeys.length; i++){
let passwordKey = allKeys[i];
chrome.storage.sync.get([allKeys[i]], function(value){
var passwordName = Object.keys(value);
passwordName = passwordName[0];
var table = document.getElementById("passwordTable");
var header = table.createTHead();
var passwordRow = header.insertRow(0);
var cellTwo = passwordRow.insertCell(0);
var cell = passwordRow.insertCell(1);
cellTwo.innerHTML = "<p1 id=passwordNameCol>" + passwordName + "</p1>";
cell.innerHTML = "<button class=copyPassword> Copy " + '"'+ passwordName + '"'+ "</button>";
});
}
});
}
The passwords clearly load in.
When I click the buttons, nothing gets sent to the console, expecting a "hello there" (as shown above)
Try these things:
the outer chrome.storage.sync.get(null returns all stored couples key+value.
Why you use chrome.storage.sync.get a second time inside the "for" statement? It is not necessary.
Don't use insert methods of table but try with createElement and appendChild.
Create first an THEAD (or TBODY) element and then put every rows on it.
When you'll finish you'll have to append only that THEAD\TBODY as child of your table.
Try to create the button with createElement (as i suggest for any other table elements) and after its creation put the event listener on it (inside the "for").
if you think to reuse the same table for other differente rows remenber to destroy the THEAD first otherwise the just created events listeners will remain orphans.
Destroy the THEAD with something like element.remove() and not with innerHTML = "".
I have a table where the sum of the rows are updated with javascript when a user changes the quantity within a popup modal. I want to be able to update the grand total of all my rows every time the total of a row change.
I have tried to add an event listener onchange and oninput on the total of each of my row but it doesn't work. I have tried to find a workaround by adding an event listener on the quantity the user is changing but to no effect. I have tried the code in the console and it is working but the event-listener doesn't seem to trigger.
document.getElementById("work_quantity_<%= #work_detail.id %>").addEventListener("change", grandTotal)
var work_detail_quantity = document.getElementById("work_quantity_<%= #work_detail.id %>").value;
var work_detail_block = document.getElementById("work-<%= #work_detail.id %>");
var quant = work_detail_block.getElementsByTagName('td')[1];
quant.innerHTML = work_detail_quantity+" <%= #work_detail.work_resource.unit_measure%>";
function grandTotal() {
var sub_total = document.getElementById("subtotal_lign").innerHTML
var table = document.getElementById("invoice_summary"), sumVal = 0;
for(var i = 1; i < table.rows.length; i++)
{
sumVal = sumVal+ parseFloat(table.rows[i].cells[4].innerText.replace(/[^0-9-.]/g, ''));
}
document.getElementById("subtotal_lign").innerHTML = sumVal.toFixed(2) + ' €' ;
};
If someone could give me a hint it would be more than appreciated, I am a rookie in javascript. Thank you
It's not a good idea to parse the DOM like that to get the values.
Solution 1
You get the object that contains the values of your table form Ruby. Something like that:
var works = <%= #works.to_json %>;
Then, when a user updates his order, you first update the object and then you update the table based on the updated object.
Solution 2
When a user update his order, you send the update to your server and you update the table in the server side.
Solution 3 (Not recommended)
If you still want to do it by parsing the DOM, have a look at MutationObserver to listen the changes in your table.
I confess, I haven't tried this per say; but I did try in jQuery a similar thing, mainly because your example lacks the detail of the table;
but you need to trigger the event when a change occurs, I assume here your not using standard input fields, so are you using like <td contenteditable></td>, they can't be monitored for change, but keyup and blur can be monitored, so if you want to trigger the calc on blur, when that event occurs, trigger your table sum event...
I'd actually use jQuery for this but here goes ...
document.getElementById("work_quantity_<%= #work_detail.id %>").addEventListener("change", grandTotal)
...
function grandTotal() {
var sub_total = document.getElementById("subtotal_lign").innerHTML
var table = document.getElementById("invoice_summary"), sumVal = 0;
for(var i = 1; i < table.rows.length; i++) {
sumVal = sumVal+ parseFloat(table.rows[i].cells[4].innerText.replace(/[^0-9-.]/g, ''));
}
document.getElementById("subtotal_lign").innerHTML = sumVal.toFixed(2) + ' €' ;
};
// something like this
(function() {
// i assume it's cell[4] that is being edited, and it's like contenteditable
var table = document.getElementById("invoice_summary");
for(var i = 1; i < table.rows.length; i++) {
/* assign listener on the cell,
when it blurs, trigger the update,
you could also monitor keyup
*/
table.rows[i].cells[4].addEventListener("blur", function(e) {
document.getElementById("work_quantity_<%= #work_detail.id %>").dispatchEvent("change", grandTotal);
});
}
})();
Updated
I thought that what I wrote below was working but actually the grand total is always one computation behind as it is doing the sum of the rows before the total of the modified row has been updated after a change of quantity.
Ok it is working with a workaround, but I still believe MutationObservers could be a good way to resolve this kind of problem in the future
Good tutorial: for MutationObservers
To make my event listener work I add to define it before the change in the quantity is made by the user (stupid isn't it)
1) Add the event listener to the object you are tracking
document.getElementById("work_quantity_<%= #work_detail.id %>").addEventListener("input", grandTotal)
2) This is where my object is changed with javascript
var work_detail_block = document.getElementById("work-<%= #work_detail.id %>");
var quant = work_detail_block.getElementsByTagName('td')[1];
quant.innerHTML = work_detail_quantity+" <%= #work_detail.work_resource.unit_measure%>";
3) I define the function which is called in the step 1
function grandTotal() {
var sub_total = document.getElementById("subtotal_lign").innerHTML
var table = document.getElementById("invoice_summary"), sumVal = 0;
for(var i = 1; i < table.rows.length; i++)
{
sumVal = sumVal+ parseFloat(table.rows[i].cells[4].innerText.replace(/[^0-9-.]/g, ''));
}
document.getElementById("subtotal_lign").innerHTML = sumVal.toFixed(2) + ' €' ;
};
I need to pass a reference to current element that is being clicked in href tag.
Consider this code which goes in a loop when creating a table
td=document.createElement("td");
td.innerHTML='click me';
But this doesn't work because it points to the browser window so when I am setting it to something the whole page replaced the value I am setting it to. So how do I pass the reference to <a> object into some_function() ?
where some_function() is declared like:
function some_function(clicked_object,param2,param3) {
clicked_object.style.backgroundColor="red"
}
Here's a full fledged solution that avoids inline code:
for (var i = 0; i < 10; i++) {
// create elements
let td = document.createElement("td");
let a = document.createElement("a");
a.innerHTML = `cell #${i}`;
a.href = ""; // we need this so <a> appears as link
// set click handler
a.addEventListener("click", some_function);
// add some data
a.dataset.rowNumber = 1;
a.dataset.extra = i * 2 + 7;
td.appendChild(a);
row.appendChild(td);
}
function some_function(e) {
e.preventDefault(); // stop default event handling
let clicked_object = e.target; // get clicked element from event
clicked_object.style.backgroundColor = "red";
console.log("param:", clicked_object.dataset.extra);
}
<table>
<tbody>
<tr id="row"></tr>
</tbody>
</table>
Edit: added data to elements
I am programmatically generating some HTML, and trying to add a listener for a change event the elements.
This works fine for the first object, but as soon as I add the second object the first one stops firing the event.
In the code example below you'll see the updateLabel function only fires for the last object created. I need it to fire for all of the objects.
I have tried with .onchange, and with an event listener, but get the same results.
Any help much appreciated.
<html>
<body>
<div id="main">
<!--this is where all the generated HTML goes -->
</div>
</body>
<script>
mainDomElement = document.querySelector("#main");
for (var count = 0; count < 4; count++)
{
var labelId = 'Label' + count;
newHTML = '<input class="accordionLabel" type="text" id="' + labelId + '" value="' + labelId + '"/>'
currentHTML = mainDomElement.innerHTML
mainDomElement.innerHTML = newHTML + currentHTML
labelDomObj = document.querySelector('#' + labelId);
//labelDomObj.addEventListener("change", updateLabel);
labelDomObj.onchange = function(event){updateLabel(event)}
}
function updateLabel(event)
{
alert(event.target.value);
}
</script>
</html>
It may be best to take a different approach when creating and adding DOM elements. Try this.
for (var count = 0; count < 4; count++)
{
var labelId = 'Label' + count,
newHTML = document.createElement('input');
newHTML.type = 'text';
newHTML.value = labelId;
newHTML.id = labelId;
newHTML.addEventListener('onchange', updateLabel, false);
mainDomElement.appendChild(newHTML);
}
your code explanation
// this takes the main DOM element and stores a copy of it in the variable.
currentHTML = mainDomElement.innerHTML
/*
This is taking the innerHTML property of the main DOM element. It is then
trying to concatenate the newly created DOM to that stored in the mainDomElement
variable. I don’t think this is what you want.
*/
mainDomElement.innerHTML = newHTML + currentHTML
// this is trying to select an element from the DOM that does not exist yet.
labelDomObj = document.querySelector('#' + labelId);
// This is trying to add an event listener to an element that does not exist.
labelDomObj.onchange = function(event){updateLabel(event)}
You are also missing sim icons from your variable declarations.
This looks like an issue with closures, where initializing var count = 0 within the for loop ultimately results in only the last value getting bound to the event handler.
I believe moving the initialization outside of the loop will fix your issue. Also, ES6 introduced the let keyword that scopes the variable in the way you are expecting:
for (let count = 0; count < 4; count++) { }
See this excellent introduction to javascript closures for more information.
I'm having a little trouble adding an event listener to elements created dynamically.
I have a list of objects that represent matches a player plays in. And a for loop that iterates through them, creating 4 buttons for each match, a +1, a +2, a -1 and an END button.
However, even if they are well created, and appended where they should be, the event listeners are not kept. Here's a sample of my code:
var container = document.getElementById('container');
for (var i = 0; i < matches.length; i++) {
var row = document.createElement("div");
row.className = "row";
var plusOne = document.createElement("a");
plusOne.id = "plusone_ " + matches[i].id;
plusOne.addEventListener('click', function () {
alert('Clicked on plusOne!')
});
...
// adding plus two, minus one and END the same way
// however, END does have an eventListener on click
row.appendChild(plusOne);
container.appendChild(row);
}
If anyone has any idea why?