dynamically add clickable row to table - javascript

I'm trying to add rows to a table in HTML using JavaScript that are clickable.
Here is my codes:
HTML:
<table border="1" id="example" style="cursor: pointer;">
<tr>
<th>1</th>
<th>2</th>
<th>3</th>
<th>4</th>
</tr>
</table>
JavaScript:
//clicked function
$('#example').find('tr').click( function(){
alert('You clicked row '+ ($(this).index()) );
});
//add new row
var x=document.getElementById('example');
var new_row = x.rows[0].cloneNode(true);
new_row.cells[0].innerHTML = "hello";
x.appendChild( new_row );
The problem is that the newly added rows are clickable but won't go through the clicked function to get the alert.
Anyone know why?

The problem is that the newly added rows are clickable but won't go
through the clicked function to get the alert.
Anyone know why?
When you are executing the initial binding of the click event to your tr elements the event is only bound to the tr elements which exist at that time in the DOM.
That is how event binding works by default. You can only bind what is currently in the DOM.
However, using jQuery 1.7+'s on() or jQuery 1.6-'s delegate() methods you can bind event with delegation.
This allows you to bind the event to the closest static parent element of the element you actual want to delegate the event to.
I'm assuming the table itself is the closest static parent element, meaning it always exists and all you add dynamically is the tr elements.
Using on() when using jQuery 1.7+ would look similar to this:
$('#example').on('click', 'tr', function(){
alert('You clicked row '+ ($(this).index()) );
});
Using delegate() when using jQuery 1.6- would look similar to this:
$('#example').delegate('tr', 'click' , function(){
alert('You clicked row '+ ($(this).index()) );
});
What this will do is bind the event to the element with id of example but delegate the click to any tr clicked within that element. As the event is delegated each time, any newly added tr elements within #example will also be included.

Try this:
Following code will take care of dynamically added rows.
//clicked function
$('#example').on('click', 'tr', function(){
alert('You clicked row '+ ($(this).index()) );
});

You are binding your click event on document.ready. New elements added after wards wil not share this binding.
Yu can acheive what you are after by using .on()
$("body").on("click", "#example tr", function(event){
alert('You clicked row '+ ($(this).index()) );
});
DEMO

is this what you are trying to achieve?
<table border="1" id="example" style="cursor: pointer;">
<thead>
<tr>
<th>1</th>
<th>2</th>
<th>3</th>
<th>4</th>
</tr>
</thead>
<tbody id="addHere"></tbody>
</table>
var addHere = document.getElementById("addHere");
var newTr;
var newTd;
function clicked(evt) {
alert("clicked tr: " + evt.target.parentNode.id);
}
for (var i = 1; i < 11; i += 1) {
newTr = document.createElement("tr");
newTr.id = "row" + i;
newTr.addEventListener("click", clicked, false);
for (j = 1; j < 5; j += 1) {
newTd = document.createElement("td");
newTd.textContent = j;
newTr.appendChild(newTd);
}
addHere.appendChild(newTr);
}
on jsfiddle
In your code it seems that you are looking for rows and then bind an event to any that you find.
You then proceed to add rows using Node.cloneNode
Cloning a node copies all of its attributes and their values,
including intrinsic (in–line) listeners. It does not copy event
listeners added using addEventListener() or those assigned to element
properties (e.g. node.onclick = fn).
so there are no event handlers bound to any of these newly added elements.
Another way to deal with this would be to use jquery delegate event handler method (on)
When a selector is provided, the event handler is referred to as
delegated. The handler is not called when the event occurs directly on
the bound element, but only for descendants (inner elements) that
match the selector. jQuery bubbles the event from the event target up
to the element where the handler is attached (i.e., innermost to
outermost element) and runs the handler for any elements along that
path matching the selector.
and do the following.
var addHere = document.getElementById("addHere");
var newTr;
var newTd;
function clicked(evt) {
alert("clicked tr: " + evt.target.parentNode.id);
}
$(document).on("click", "#addHere tr", clicked);
for (var i = 1; i < 11; i += 1) {
newTr = document.createElement("tr");
newTr.id = "row" + i;
for (j = 1; j < 5; j += 1) {
newTd = document.createElement("td");
newTd.textContent = j;
newTr.appendChild(newTd);
}
addHere.appendChild(newTr);
}
on jsfiddle

Related

Need help translating this JQuery code back to raw Javascript

I'm currently having trouble understanding what's going on with this code
$("#table").on("click", ".plusRow", function(event){
var name = this.getAttribute("table-data");
tableData.addData(name, 0, 1);
displayTable();
});
I understand that the first part should go something along the lines of
document.getElementById("table").addEventListener("click", function(event)
but im having trouble understanding where the ".plusRow" class should go, is it added onto the eventlistener? or how would this code be better translated back to regular Javascript.
This code snippets binds a listener on a single element (the table) and delegates it to its children which means that it will only run the event handler when it bubbles up to one or multiple elements that match the predicate (having a "plusRow" class).
With event delegation you could do:
let table = document.getElementById('table');
table.addEventListener('click', event => {
const elem = event.target;
if (elem.classList.contains('plusRow')) {
const name = elem.getAttribute("table-data");
tableData.addData(name, 0, 1);
displayTable();
}
});
Here we have to keep in mind that this code will always run when a child of the table is clicked but will only update the table when the target matches the predicate.
Without using event delegation you could do the following which will have similar results but behaves quite differently:
let tableElem = document.getElementById('table');
// To keep this simple we assume there is only one button
let button = tableElem.getElementsByClassName('plusRow')[0];
button.addEventListener('click', event => {
const name = event.currentTarget.getAttribute("table-data");
tableData.addData(name, 0, 1);
displayTable();
})
This version will only ever run when the first child of the table with a class of "plusRow" is clicked. Please note that this is just an example because if there is no element with such class an exception will be raised when we try to bind the event listener.
So I've come up with a dummy possible solution example using querySelector and querySelectorAll. Let me know if anyone sees an issue with the suggested solution.
function delegate(parentSelector, eventType, childSelector, callback) {
//lookup the parent element
var parent = document.querySelector(parentSelector);
//put the event listener for the event on the parent
parent.addEventListener(eventType, function(event) {
//get the element that caused the event
var element = event.target;
//find all the children in the parent that match the child selector,
//at this point in time
var children = parent.querySelectorAll(childSelector);
//if the element matches a child of the parent that matched the
//childSelector, we want to do our callback
for (var i = 0; i < children.length; i++) {
if (children[i] === element) {
callback();
break;
}
}
});
}
delegate('#table', 'click', '.addRow', function() {
document.querySelector('#table').innerHTML += `
<tr>
<td>Something</td>
<td><button class="addRow">Add Row</button></td>
</tr>
`;
});
<table id="table">
<tr>
<td>Something</td>
<td><button class="addRow">Add Row</button></td>
</tr>
</table>

Showing Hidden Row in Table

I am using some code based on the following JSFiddle. The intention is to show more information when the user clicks the "Show Extra" link.
The problem that I'm having is that when the link is clicked on all but the bottom row of the table the hidden element is shown briefly and then closes.
I am populating my table using template strings in javascript. Here is the code that I use to add rows to the table:
this.addRecordToTable = function(bet, index, id){
console.log(index);
console.log($.data(bet));
var butId = id.toString();
if (bet.bookies == null){
bet.bookies = "";
}
if (bet.bet == null){
bet.bet = "";
}
var newRow = `
<tr>
<td>${bet.date}</td>
<td>${bet.bookies}</td>
<td>${bet.profit}</td>
<td><button id=${butId}>Delete</button></td>
<td>Show Extra</td>
</tr>
<tr>
<td colspan=\"5\">
<div id=\"extra_${index}\" style=\"display: none;\">
<br>hidden row
<br>hidden row
<br>hidden row
</div>
</td>
</tr>
`
console.log(newRow);
console.log("#"+butId);
$(newRow).appendTo($("#betTable"));
$("#"+butId).click(
function()
{
if (window.confirm("Are you sure you want to delete this record?"))
{
var rec = new Records();
rec.removeRecordAt(index);
$("#betTable tbody").remove();
var c = new Controller();
c.init();
}
});
$("a[id^=show_]").click(function(event) {
$("#extra_" + $(this).attr('id').substr(5)).slideToggle("slow");
event.preventDefault();
});
}
EDIT:
I had to change $("a[id^=show_]").click to $("a[id=show_"+index).click..., as the event handler was being added to each element every time I added a new element. Thanks to #freedomn-m.
This code:
$("a[id^=show_]")
adds a new event handler to every existing link as well as the new one as it's not ID/context specific so all the show a's match the selector.
You need to add the context (newRow) or use the existing variable(s) as part of the loop that are already defined, eg:
$("a[id^=show_]", newRow)
$("a#show_" + index)
(or any other variation that works).
An alternative would be to use even delegation for the dynamically added elements, eg:
$(document).on("click", "a[id^=show_]", function...
in which case you'd only need to define/call the event once and it would be fired for new elements (ie put that outside the new row loop).

Assigning an event to a <td> cell via a function

I'm trying to understand why I can add certain items to a cell, such as 'id', and not other items such as an onclick. My goal is to have a button pressed, which adds a row to a table (which works) - and set some values on the that is generated/appended to the table. I've noticed that I can step into the console and do:
rows[row_#].cells[cell_#].id = 'foo';
and have it appear in the table on the and function; but the following will not appear on the :
rows[row_#].cells[cell_#].onclick = 'callEvent(this)';
Should I be assigning this differently?
<button type="button" id="btn_add_row" onclick="addRow()">Add Row</button>
<table class="table table-hover" id="sample_table">
<thead>
<th>Column A</th>
<th id='calculate'>Column B</th>
</thead>
<tbody>
<tr>
<td>Item 1</td>
//sample of the td I'd like the function to generate
<td id='calculate' onclick='callEvent(this)'>Item 2</td>
</tr>
</tbody>
</table>
<script type="text/javascript">
// Code to add a row to the table and assign properties to new row
function addRow() {
var table = document.getElementById("sample_table");
var lastRow = table.length;
var numberOfCols = table.rows[0].cells.length;
var row = table.insertRow(lastRow);
for (var i=0;i<numberOfCols;i++) {
row.insertCell(i);
if (table.rows[0].cells[i].id === 'calculate') {
// The calculate id will appear on the TD after running
table.rows[i].id = 'calculate';
// The onclick event will not appear on the TD afer running
table.rows[i].onclick='callEvent(this)';
}
function callEvent(element) {
console.log('Calculate event fired!');
}
</script>
The biggest issue is that you are not supplying a callback function reference to your onclick property. You are supplying a string:
.onclick='callEvent(this)'
So, no function actually gets invoked when the click event occurs.
Next, you shouldn't be using event properties (like onclick) in your JavaScript or adding inline HTML event handling attributes at all (that technique is about 20 years old) as they:
Create "spaghetti code" that is difficult to read and debug.
Lead to duplication of code.
Don't scale well
Don't follow the separation of concerns development methodology.
Create anonymous global wrapper functions around your attribute values that alter the this binding in your callback functions.
Don't follow the W3C Event Standard.
Instead, do all your work in JavaScript and use .addEventListener() to set up event handlers.
Also (FYI) id attributes need to be unique, so when you create a new row or cell, don't reuse an already assigned id.
Here's an example:
// Place all of this inside of a <script> element that is just before the
// closing of the body (</body>)
// Get references to all elements that you'll be working with
var btnAddRow = document.getElementById("btn_add_row");
var tbl = document.getElementById("sample_table");
// Now, set up the event handling functions
btnAddRow.addEventListener("click", addRow);
// Code to add a row to the table and assign properties to new row
function addRow() {
var counter = 1; // id attributes must be unique. This will keep it that way.
var numberOfCols = tbl.rows[0].cells.length;
var row = tbl.insertRow();
for (var i = 0; i < numberOfCols; i++) {
var cell = row.insertCell(i);
cell.id = "row" + (tbl.rows.length - 1) + "cell" + counter;
// Now, we'll create a new button, place that button in the new cell and
// set up a click event handler for it.
var btn = document.createElement("button");
btn.textContent = cell.id;
btn.id = "btn" + tbl.rows.length + counter;
// Add a click event handler
btn.addEventListener("click", function(){
alert("You clicked cell: " + this.id);
});
// And now include the button in the cell
cell.appendChild(btn);
counter++; // Increment the counter after using it
}
}
td { border:1px solid black; }
td:nth-child(2) { cursor:pointer; }
<button type="button" id="btn_add_row">Add Row</button>
<table class="table table-hover" id="sample_table">
<thead>
<th>Column A</th>
<th id='calculate'>Column B</th>
</thead>
<tbody>
<tr>
<td>Item 1</td>
<!-- sample of the td I'd like the function to generate -->
<td id='calculate'>Item 2</td>
</tr>
</tbody>
</table>
Two things:
The onclick expects a function. So to solve your problem, change
table.rows[i].onclick='callEvent(this)';
to
table.rows[i].onclick=callEvent;
The second thing is, the parameter on an event is actually the event, and this refers to the element:
function callEvent(event) {
console.log('Calculate event fired!');
// "event" is the event
// "this" is the element
}
missing need to second bracket end and use this callEvent(this) without single inverted comma.
Like this...
<script type="text/javascript">
// Code to add a row to the table and assign properties to new row
function addRow() {
var table = document.getElementById("sample_table");
var lastRow = table.length;
var numberOfCols = table.rows[0].cells.length;
var row = table.insertRow(lastRow);
for (var i=0;i<numberOfCols;i++) {
row.insertCell(i);
if (table.rows[0].cells[i].id === 'calculate') {
// The calculate id will appear on the TD after running
table.rows[i].id = 'calculate';
// The onclick event will not appear on the TD afer running
table.rows[i].onclick=callEvent(this);
}
}
}
function callEvent(element) {
console.log('Calculate event fired!');
}
</script>

Dynamically adding event handler to select box

I'm trying to dynamically add an event listener to a select box.
With this code I just don't get any response, so no alert box:
var table = $('<table></table>');
var row = $('<tr><td></td></tr>').html('<select id="sel1"><option>test</option><option>test2</option></select>');
$("sel1").on('change', function() {
alert(this.val());
});
table.append(row);
$('#mydiv').append(table);
Also, how can I add the select box between the td?
Currently, it's added between the tr, td simply isn't there.
Here is a fiddle
Updated Fiddle
You should use event delegation on() when you deal with fresh DOM added dynamically :
$("#mydiv").on('change', '#sel1', function() {
alert($(this).val());
});
NOTES :
You should add id selector before sel1 it should be #sel1.
.val() is a jquery method you can't call it on javascript object like this.val() it should be $(this).val().
The current code will not add select inside td it will add it directely inside tr tag so you could replace :
var row = $('<tr><td></td></tr>').html('<select id="sel1"><option>test</option>
<option>test2</option></select>');
By :
var row = $('<tr></tr>').html('<td><select id="sel1"><option>test</option><option>
test2</option></select></td>');
Hope this helps.
Working Snippet
var table = $('<table></table>');
var row = $('<tr></tr>').html('<td><select id="sel1"><option>test</option><option>test2</option></select></td>');
$("#mydiv").on('change', '#sel1', function() {
alert($(this).val());
});
table.append(row);
$('#mydiv').append(table);
td{
border: 1px solid;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="mydiv"></div>
Couple of points to note in your code
1) Wrong Selector $("sel1")
The problem in your code is $("sel1") you need to select by id using # so it should be $("#sel1"). So your code would be like
$("#sel1").on('change', function() {
alert(this.val());
});
2) Bind event after appending the HTML to DOM or Use Event Delegation
Your code should be places in this order Working Fiddle
var table = $('<table></table>');
var row = $('<tr><td></td></tr>').html('<select id="sel1"><option>test</option><option>test2</option></select>');
table.append(row);
$('#mydiv').append(table);// now the element is added to DOM so bind event
$("#sel1").on('change', function() {
alert($(this).val()); // note here I changes this.val() to $(this).val()
});
Or another option is using event delegation Working Fiddle
To add event's to dynamic elements use the event delegation
$('body').on('change',"#sel1", function() {
alert($(this).val());
});
3) To place the select tag inside td use the below syntax
var row = $('<tr></tr>').html('<td><select id="sel1"><option>test</option><option>test2</option></select></td>');
Wrap the td along with the select tag and not inside the tr Working Fiddle
You need to bind the change event after appending to the page:
var table = $('<table></table>');
var row = $('<tr><td></td></tr>').html('<select id="sel1"><option>test</option><option>test2</option></select>');
table.append(row);
$('#mydiv').append(table);
$("#sel1").on('change', function() {
alert(this.val());
});
And also you have forgotten the # id selector for "sel1"
There are three major issues in your code
1.The id selector must start with #
$("#sel1").on('change', function() {
2.You should bind the change listener only after you appended the element, because it just doesn't exist in the DOM before
3.With
$('<tr><td></td></tr>')
you'll get a jquery reference to the row (the <tr> element). Then with .html() you are replacing the content of the row (including the <td> of course)

What happens to the event handler?

Example: A table with 1 row and 1 cell. Javascript gives this one cell and event handler which will append new rows.
<table border=1>
<tbody id="target">
<tr>
<td class="hi" >I append</td>
</tr>
</tbody>
</table>
var els = document.getElementsByClassName("hi");
for(i=0;i<els.length;i++){
els[i].onclick = function(){callMe(this)};
}
function callMe(t){
var el = document.getElementById("target");
el.innerHTML += '<tr><td class="hi" >appended...</td></tr>';
}
The callMe function gets called once, a new row is appended, the old row stays the same(I suppose).
The second time the first cell is clicked the function does not get called. Why?
What happened there?
What am I missing?
http://jsfiddle.net/2U3m3/1/
I am not using any libraries, just plain JavaScript. I want the first cell to be clickable always. It is meant to add rows forever not just one.
The second time the first cell is clicked the function does not get called. Why?
Because overwriting the innerHTML of an element re-recreates all child elements, no matter if you just “append” to the innerHTML using +=.
And that the table cell has gotten replaced by a new one means that the event handler bound to the old table cell is also gone.
The callMe function gets called once, a new row is appended, the old row stays the same(I suppose).
You “suppose” wrong here.
You need to use DOM methods for row/cell insertions if you plan to keep original event:
function callMe(t){
var el = document.getElementById("target");
var row = el.insertRow(el.rows.length);
var cell = row.insertCell(0)
cell.innerHTML = "Hi"
}
Demo: http://jsfiddle.net/ygalanter/2U3m3/2/
You can delegate the event to the class name directly. So any element with that class will have the click event including newly created ones. Adapted from the answer here.
if (document.body.addEventListener) {
document.body.addEventListener('click',clickHandler,false);
} else {
document.body.attachEvent('onclick',clickHandler); //for IE
}
function clickHandler(e)
{
e = e || window.event;
var target = e.target || e.srcElement;
if (target.className.match(/hi/)) //or whatever classname you want
{
callMe(target);
}
}

Categories