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>
Related
This is my first SO post, please let me know how to do better!
I have a function that clears the by setting the opacity to 0, it works, but it will make my file massive if if try to set up a whole spread sheet with each having the same function bar different ids,
Ideally, the way I want this to play out, is that clears itself, and will clear all blocks. And I want to do it without having to write duplicate functions.
Is it possible to have a function set over classes? I have tried with no success
Or is there a better way to run the JavaScript, like somehow onclick==clear.self ?
function Xf1() {
f1();
f2();
}
function f1() {
var element = document.getElementById("a1");
element.style.opacity = "0";
}
function f2() {
var element = document.getElementById("a2");
element.style.opacity = "0";
<tr>
<th onclick="Xf1()">Clear all</th>
</tr>
<tr>
<td onclick="f1()" id="a1"> text1</td>
<td onclick="f2()" id="a2"> text2</td>
</tr>
You can use event delegation
start by adding a class to the table element
add a class to the "clear all" heading
add a click event listener to the table element
If the click event target is a td element, set its opacity to 0.
If the click target is the clear all heading, set all td elements to opacity 0. You can do that by querying the table for td tags and then using forEach to change the opacity for each of them.
const myTable = document.body.querySelector(".my-table");
myTable.addEventListener("click", event => {
const target = event.target;
if (target.tagName == "TD") {
target.style.opacity = 0;
}
if (target.classList.contains("clear-all")) {
myTable.querySelectorAll("td").forEach(item => (item.style.opacity = 0));
}
});
<table class="my-table">
<thead>
<th class="clear-all">Clear all</th>
</thead>
<tbody>
<tr>
<td id="a1">text1</td>
<td id="a2">text2</td>
</tr>
</tbody>
</table>
set an id to parent tag then set onclick to that elements children.
if you set "container" as id you will have something like this:
var elements = document.getElementById("container");
for (let i = 0; i < elements.children.length; i++) {
elements.children[i].onclick = function () {
elements.children[i].style.opacity = "0";
};
}
Instead of adding onclick to each td element. Have it in table element. Like this
document.querySelector("#table").addEventListener("click", (event)=>{
if(event.target.dataset.type==='clear'){
const ids = ['a1', 'a2'];
ids.forEach((ele)=>{
document.querySelector(`#${ele}`).style.opacity = '0';
});
return;
}
event.target.style.opacity = "0";
}
)
<table id="table">
<tr>
<th data-type="clear">Clear all</th>
</tr>
<tr id="tableRows">
<td id="a1"> text1</td>
<td id="a2"> text2</td>
</tr>
</table>
I have been given a table that has been created using the DOM and now I have to use if statements to print specific areas of that table. For example in the second photo, when i click 1 - 2 million, it should show the table but only display the countries that have a population that's between 1 and 2 million. My teacher has barely taught us JavaScript deeply and now gives us a DOM assignment that uses JavaScript If Statements. I would appreciate if someone could give an explanation on how i can print specific parts of the table when i click the links/button above. Thanks!
Here a roadmap:
Loop through your submenus with a for (or for ... in) statement and attach a click event listener on each one with addEventListener()
In the callback, this will refer to the <li> (or <a>, or whatever) element you clicked (and which is linked to an event). So you can access the DOM clicked element's data nor attributes.
In function of the clicked submenu, filter your <table> the way you want thanks to if statements. (even better: switch statement) Visually, rows will be hidden. In Javascript, you will update style attribute of the element.
Below an example. I propose to you to try to do it yourself with elements I gave you. Open the snippet if you are really lost.
Exemple:
Other functions/methods/statements I used below: querySelectorAll(), dataset, instanceof, parseInt(), onload, children
// Always interact with the DOM when it is fully loaded.
window.onload = () => {
// Gets all <button> with a "data-filter-case" attribute.
const buttons = document.querySelectorAll('button[data-filter-case]');
// For each <button>, attach a click event listener.
for (let i in buttons) {
const button = buttons[i];
// The last item of "buttons" is its length (number), so we have to check if
// it is a HTMLElement object.
if (button instanceof HTMLElement) {
button.addEventListener('click', filterTable); // Will execute the "filterTable" function on click.
}
}
}
// The function which will filter the table.
function filterTable(e) {
// Useless in my exemple, but if you have <a> instead of <button>,
// it will not execute its original behavior.
e.preventDefault();
// Get the value of "data-filter-case" attribute.
const filterCase = this.dataset.filterCase;
// Get all table body rows.
const tableRows = document.querySelectorAll('table > tbody > tr');
// Update display style of each row in function of the case.
for (let i in tableRows) {
const row = tableRows[i];
if (row instanceof HTMLElement) {
if (filterCase === 'more-than-44') {
if (parseInt(row.children[1].innerText) <= 44) {
// Hide the row.
row.style.display = 'none';
} else {
// Reset display style value.
row.style.display = '';
}
} else if (filterCase === 'less-than-27') {
if (parseInt(row.children[1].innerText) >= 27) {
row.style.display = 'none';
} else {
row.style.display = '';
}
} else if (filterCase === 'reset') {
row.style.display = '';
}
}
}
}
<button type="button" data-filter-case="more-than-44">More than 44</button>
<button type="button" data-filter-case="less-than-27">Less than 27</button>
<button type="button" data-filter-case="reset">Show all</button>
<table>
<thead>
<tr>
<th>ID</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<th>1</th>
<td>45</td>
</tr>
<tr>
<th>2</th>
<td>50</td>
</tr>
<tr>
<th>3</th>
<td>24</td>
</tr>
</tbody>
</table>
After I click 5 times on table:eq(0) td I want to disable the first function and after i click 2 times on table.dvojka td I want to disable the second function.
$("table:eq(0) td").click(function () {
$(this).addClass("tdbarva");
});
$("table.dvojka td").click(function () {
$(this).addClass("barvica");
});
You'd first need to create at least two global counters that keeps track of the clicks. Then in each click event handler you'd have to check if the clicks match your threshold. From there you use off() to remove the event handlers from each <td/>.
let clickCountOne = 0;
let clickCountTwo = 0;
$("table:eq(0) td").click(function() {
clickCountOne++;
if (clickCountOne === 5) {
console.log('Click handler has been disabled for first table td');
$(this).off('click');
}
$(this).addClass("tdbarva");
});
$("table.dvojka td").click(function() {
clickCountTwo++;
if (clickCountTwo === 2) {
console.log('Click handler has been disabled for second table td');
$(this).off('click');
}
$(this).addClass("barvica");
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table>
<td>Click Me</td>
</table>
<table class="dvojka">
<td>Click Me Too</td>
</table>
I might suggest using a data attribute on the table itself to keep count of clicks. When either table is clicked, its count attribute is updated. Then its event listener is called, which compares that dynamic count against a static limiter (manually typed into the DOM node). If the count exceeds the limit, i use the jQuery off() function to remove that table's event handler.
Hope this helps!
// Set my click counter to zero for all tables...
$("table").each(function() {
$(this).data("clickCount", 0);
});
// Create the references to each table element.
var firstTable = $("table:eq(0)");
var secondTable = $("table.dvojka");
// Attach my event listeners...
firstTable.on("click", "td", firstFunc);
secondTable.on("click", "td", secondFunc);
/****
* Each table will maintain its own click count data attribute.
*
****/
$("table td").on("click", function() {
var clickedTable = $(this).parents("table");
var clickCount = parseInt(clickedTable.data("clickCount")) + 1;
var clickLimit = clickedTable.attr("data-clickLimiter");
clickedTable.data("clickCount", clickCount);
});
/*****
* The following functions are used in the event listeners for the
* tables, and are tracking their own count to determine when to
* disable themselves.
*****/
function firstFunc(evt){
// the clickCount is dynamic, created by the program itself.
// The clickLimiter is a static attribute, defined on the DOM node manually.
var clickCount = parseInt(firstTable.data("clickCount"));
var clickLimit = parseInt(firstTable.attr("data-clickLimiter") );
// Has the count exceeded our limit?
if(clickCount >= clickLimit){
// If it has, remove the event listener.
firstTable.off("click", "td", firstFunc);
}
console.log("You've clicked the first table "+
clickCount +
" times. It has a limit of " +
clickLimit +
" clicks, or " +
parseInt(clickLimit-clickCount) +
" remaining");
}
function secondFunc(){
var clickCount = parseInt(secondTable.data("clickCount"));
var clickLimit = parseInt(secondTable.attr("data-clickLimiter") );
if(clickCount >= clickLimit){
secondTable.off("click", "td", secondFunc);
}
console.log("You've clicked the second table "+
clickCount +
" times. It has a limit of " +
clickLimit +
" clicks, or " +
parseInt(clickLimit-clickCount) +
" remaining");
}
.dvojka {
background-color: #ccc;
border: 1px solid black;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table data-clickLimiter=5>
<thead>
<tr>
<th>Foo</th>
<th>bar</th>
<th>baz</th>
</tr>
</thead>
<tbody>
<tr>
<td>01-01</td>
<td>01-02</td>
<td>01-03</td>
</tr>
<tr>
<td>02-01</td>
<td>02-02</td>
<td>02-03</td>
</tr>
<tr>
<td>03-01</td>
<td>03-02</td>
<td>03-03</td>
</tr>
</tbody>
</table>
<table class="dvojka" data-clickLimiter=2>
<thead>
<tr>
<th>Foo</th>
<th>bar</th>
<th>baz</th>
</tr>
</thead>
<tbody>
<tr>
<td>01-01</td>
<td>01-02</td>
<td>01-03</td>
</tr>
<tr>
<td>02-01</td>
<td>02-02</td>
<td>02-03</td>
</tr>
<tr>
<td>03-01</td>
<td>03-02</td>
<td>03-03</td>
</tr>
</tbody>
</table>
Note that this could just as easily have been done with a single click handler for both tables, and it would only disable for the appropriate table -- but I can only assume that the processing for both tables would somehow be different. If both tables have the exact same click functionality, then it would be a trivial matter to remove the second table's click handler, and simply have the one function for all tables.
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).
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