I'm writing a script which manages a very large table. When a user clicks a table cell, I would like to know which cell they clicked.
e.g
--------------
| | |
| | Click|
--------------
should give me a cell reference of (1, 1).
Any way I could do this with javascript. The page it's running on uses jquery for other purposes, so any jquery based solutions are good aswell.
EDIT: To clarify, the top left cell is (0, 0). For performance reasons, the event needs to bound to the table, not the tds.
This is done very easily using the target property of the event object:
$('#mytable').click(function(e) {
var tr = $(e.target).parent('tr');
var x = $('tr', this).index(tr);
var y = tr.children('td').index($(e.target));
alert(x + ',' + y);
});
This approach allows you to only bind 1 event handler to the entire table and then figure out which table cell was clicked. This is known as event delegation and can be much more efficient in the right situation, and this one fits the bill. Using this you avoid binding an event to each <td>, and it does not require hard-coding coordinates. So, if your table looks like this:
<table id='mytable'>
<tr>
<td>hi</td>
<td>heya</td>
</tr>
<tr>
<td>boo</td>
<td>weee</td>
</tr>
</table>
It will alert the coordinates on click. You can do whatever with that. :)
If you find performance to be too slow (depending on just how large your table is) you would then have to resort to hardcoding or a combination of the two, maybe only hard coding the <tr> index, as that would be the slowest to get, and then getting the <td> index dynamically. Finally, if all this coordinate business is completely unnecessary and what you really just wanted was a reference to the clicked <td>, you would just do this:
$('#mytable').click(function(e) {
var td = $(e.target);
// go crazy
});
$('td').click(function(event) {
var row = $(this).parent('tr');
var horizontal = $(this).siblings().andSelf().index(this);
var vertical = row.siblings().andSelf().index(row);
alert(horizontal+','+vertical);
});
Walk the DOM hierarchy...
You should be able to do this by obtaining a reference to the DOM node for the clicked TD, then walking the previousSibling chain backwards until you reach the first column, counting as you go.
One you're at the start of that sibling chain (i.e., the first TD in the row), the parentNode should be the TR node, and with that you can perform a similar walk through previousSibling TRs to count up the number of rows.
..or tag each cell with additional attributes
Alternatively, when you create the table, add some fake rowidx="??" and colidx="??" attributes to each cell and retrieve these with getAttribute. If you want to use a legal attribute, you could put the row and column index in an axis="??,??" attribute.
You mentioned a very large table - it would be unwise to bind the click to every td. A better approach is to use the .live method, if your still on jquery 1.2.6 you can just bind a click event to the table and use event delegation. if you need code for this ask.
$("#tableId td").live('click', function () {
var $this = $(this);
var x = $this.prevAll().length;
var y = $this.parent().prevAll.length;
console.log(x + ", " + y);
});
assuming your 1st row, 1st column to be 0,0
var row = 0;
$('#tblImages > tbody > tr').each( function(){
var col = 0;
$(this).children('td').each( function(){
$(this).attr("currentRow", row).attr("currentCol", col);
col++;
});
row++;
}
);
$('#tblImages > tbody > tr > td').click(function(){
alert( $(this).attr("currentRow") + " " + $(this).attr("currentCol"));
This can certainly be improved more..but its working
window.onload = function () {
document.getElementsByTagName('table')[0].addEventListener('click', function(element) {
var rowIndex = element.target.parentElement.rowIndex;
var cellIndex = element.target.cellIndex;
document.getElementById('alert').innerHTML = ('Row index = ' + rowIndex + ', Column index = ' + cellIndex);
}, false);
}
tr, th, td {
padding: 0.6rem;
border: 1px solid black
}
table:hover {
cursor: pointer;
}
<table>
<tbody>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>4</td>
<td>5</td>
<td>6</td>
</tr>
</tbody>
</table>
<p id="alert"></p>
Related
I'm trying to hide a table row with javascript and css.
I have to admit that I'm still a beginner, so it's likely that I'll ask some stupid questions.
I want to hide a table row that somewhere in a td contains the text 'banana'.
Hopefully someone can help me out, thanks!
I've tried different kinds of code I found on the internet, but can't get anything working. This is what I got so far.
if(document.getElementsByTagName('tr').contains('banana'))
{
document.getElementsByTagName('tr').style.display = 'none';
}
Problem you have is you seem to be thinking in terms of jQuery. JQuery does loops under the hood. Since you are not in jQuery world, you need to do the loops yourself over the collection.
Select the rows
loop over the rows
Read the text
Check if text as match
if it does, hide it
// select all the rows
const rows = document.querySelectorAll('tr');
// loop over the rows
rows.forEach( function (row) {
// get the text of the row
var text = row.textContent; // case insensitive use .toLowerCase()
// see if it is in the string
if (text.includes('banana')) {
// add class to hide the row
row.classList.add('hidden')
}
})
.hidden {
display: none;
}
<table>
<tbody>
<tr>
<td>apple</td><td>$1.00</td>
</tr>
<tr>
<td>pear</td><td>$1.00</td>
</tr>
<tr>
<td>banana</td><td>$1.00</td>
</tr>
<tr>
<td>strawberry</td><td>$1.00</td>
</tr>
</tbody>
<table>
How will this fail? When the text joins between cells there is no whitespace so you can make matches that are not there. Is you search for something that is partial sting in another word that can also be wrong.
Other option you have is instead of looping over the rows, you loop over the cells. And if there is a match in the td, you hide the parent.
Instead of getting tr element you can get all td element and perform the following:
var tdcollection = document.getElementsByTagName('td');
for (var i = 0; i < tdcollection.length; i++) {
if (tdcollection[i].innerText.indexOf("banana") >= 0) {
tdcollection[i].parentElement.style.display = 'none';
}
}
<table>
<tr><td>banana</td><td>test1</td></tr>
<tr><td>grape</td><td>test2</td></tr>
</table>
I am trying to remove a td at runtime using Javascript only (no JQuery intended). Here is my table :
<table id = "tab">
<tr>
<td>
test 1
</td>
<td>
test 2
</td>
<td>
test 3
</td>
</tr>
</table>
Here is what I tryied :
function removeFirstTD() {
var element = document.getElementById("tab");
element.removeChild(element.firstChild);
}
Note that this function works and I execute it by doing this :
<body onload = "removeFirstTD();">
<!-- the tab is here -->
</body>
The problem is that it seems to erase the <tr> because I didn't scope the tr before requesting remove the td. May someone help me doing this please ?
Maybe the simplest way is to use HTMLTableElement Interface:
function removeFirstTD() {
var element = document.getElementById("tab");
element.rows[0].deleteCell(0);
}
A live demo at jsFiddle.
Use can also use querySelector. It picks the first element that matches it like this;
function removeFirstTD() {
var element = document.querySelector("#tab tr td");
element.remove();
}
Go down one more level?
element.firstChild.removeChild(element.firstChild);
Due to white space (between the table and tr, and tr and td) this creates TEXTNODES which will be the first children, you would have to be more specific:
function removeFirstTD() {
var element = document.getElementById('tab');
var firstRow = element.getElementsByTagName('TR')[0];
var firstColumn = firstRow.getElementsByTagName('TD')[0];
firstColumn.remove();
}
But you might find this simpler and more reliable:
function removeFirstTD() {
document.querySelector('#tab tr td').remove();
}
You're close. td elements belong to the tr elements (and technically the tr elements should belong to a tbody element, not the table directly). You need to get the tr elements then find their first children.
function removeFirstTD() {
var element = document.getElementById("tab");
var tr = element.getElementsByTagName('tr');
var j = tr.length;
while (j--){
tr[j].removeChild(tr[j].firstChild);
}
}
Note that firstChild might be whitespace or some other entity that is no the first td element, and you should add a check for that before removing that child.
<table class="u-full-width">
<thead>
<tr>
<strong><td>Joke</td></strong>
</tr>
</thead>
<tbody>
<tr>
<td>This is a joke</td>
</tr>
</tbody>
</table>
I'm adding this to give a step by step procedure for beginners like me looking for this answer.
Let's say above is how our table looks
First, get a DOM reference to the table body let tbody = document.querySelector('u-full-width').getElementsByTagName('tbody')
The above statements return a HTML Collection. So, we take the zero index of that collections tbody = tbody[0]
tbody.removeChild(tbody.firstElementChild) firstElementChildignores text and comment nodes
What I want to do, in short, is from $(this) being a table row, find the next table row with a class of "example" (not necessarily a sibling).
I.E. use next() to find the next row with a class of "example" which isn't a sibling.
HTML:
<table>
<tr><td>One</td></tr>
<tr class="current"><td>Two</td></tr>
<tr><td>Three</td></tr>
<tr><td>Four</td></tr>
<tr class="target"><td>Five</td></tr>
<tr><td>Six</td></tr>
</table>
JavaScript:
var current = $('.current').next();
while(current.size() && !current.hasClass('target')) {
current = current.next();
}
current.css('color', '#0f0');
OR
$('.current').nextAll('.target').last().css('color', '#0f0');
If you're building those <tr>s from strings, you could do something like that:
var x = '';
for(var i = 0; i < 3; i++) {
x += '<li>Test ' + i + '</li>';
}
$(x).appendTo('#test');
So instead of inserting the table rows one by one, put them together as one string, make a jQuery object from that string and attach that to your table.
This also helps you with performance, since you edit the DOM only once.
To change HTML table so that some cell has given rowspan="n" (to increase rowspan) you have to delete n cells below the one that is getting extended.
The original HTML source (HTML structure) looks like this
<table border="1">
<tr id="1"><td>1</td><td>Zubenelgenubi</td></tr>
<tr id="2"><td>2</td><td>Algorab</td></tr>
<tr id="3"><td>3</td><td>Almach</td></tr>
<tr id="4"><td>4</td><td>Alula_Borealis</td></tr>
<tr id="5"><td>5</td><td>Rigil_Kentaurus</td></tr>
<tr id="6"><td>6</td><td>Menkent</td></tr>
</table>
and I like to transform it to something like this:
<table border="1">
<tr id="1"><td>1</td><td>Zubenelgenubi</td></tr>
<tr id="2"><td>2</td><td>Algorab</td></tr>
<tr id="3" rowspan="3"><td>3</td><td>Almach</td></tr>
<tr id="4"> <td>Alula_Borealis</td></tr>
<tr id="5"> <td>Rigil_Kentaurus</td></tr>
<tr id="6"><td>6</td><td>Menkent</td></tr>
</table>
Unfortunately SO formatting doesn't support tables, neither in Markdown, nor in HTML.
Is it possible to do it without causing unnecessary reflow? I mean here something better than simply
for (var i = 0; i < numlines; i++) {
...
if (i === 0) {
td.rowSpan = numlines;
...
} else {
tr.deleteCell(0); // or td.parentNode.removeChild(td);
}
}
which I think causes reflow after each iteration.
When adding elements one can use DocumentFragment; what to do when modifying number of elements at once?
Edit: added 03-05-2011
A solution using Range object (the W3C DOM version)
var range = document.createRange();
range.setStartBefore(document.getElementById(start+''));
range.setEndBefore(document.getElementById(start+numlines+''));
var fragment = range.cloneContents();
for (var i = 0; i < numlines; i++) {
var rownum = start + i;
var row = fragment.getElementById(rownum.toString()); // not always work
var td = row.firstChild;
if (i === 0) {
td.style.backgroundColor = 'yellow';
td.rowSpan = num.toString();
} else {
td.parentNode.removeChild(td);
}
}
range.deleteContents();
var rowAfter = document.getElementById(start+num+'');
rowAfter.parentNode.insertBefore(fragment, rowAfter);
Note that for some reason fragment.getElementById didn't work for me, so I had to cheat knowing what nodes are there.
deleteContents + insertBefore is needed because table.replaceChild(range, fragment); does not work, unfortunately (where table is element from which range was extracted).
Try to make the table element display:none
before the loop and restore the
display after.
Another option would be to assign
fixed dimensions and use
overflow:hidden for the the time of loop body.
This should isolate update tree by
the table only. Theoretically.
And the last is to compose HTML of
the table and replace the table as
whole - this will be made in single
transaction.
the following question might be an easy one for most of you. But I am just beginning JavaScript and I really would appreciate if someone could point me in the right direction.
I have a normal HTML table where each row has its own id. What I want to achieve is that each time I click a link in a row the id for that row should be stored in a variable and later passed to a function. Let's say I click row#1 then id#1 should be passed and so on... How can I achieve this?
Thank you.
This should work:
var currentRowID, table = document.getElementById('your-table');
var rows = table.rows;
var row_count = rows.length;
for(var i = 0; i < row_count; i++) {
rows[i].onclick = function() {
currentRowID = this.id;
//do something with currentRowID here...
};
}
jsFiddle example
When you arrange for an event handler to respond to clicks, the browser will set things up so that you can figure out what element was clicked (the "target" of the event). In your Javascript, you could do something like this, assuming your <table> is the only table on the page:
function handleRowClicks(e) {
e = e || window.event;
if (!e.target.tagName.toLowerCase() === "tr") return;
var rowId = e.target.id;
/*
whatever you want to do goes here
*/
}
// To set up the event handler, do this in your "window.onload" or some
// other initialization point
document.getElementsByTagName("table")[0].onclick = handleRowClicks;
This is only one of a bunch of different approaches. If you were to use a Javascript framework/library, it would get a little simpler, probably, but not much.
Note that this approach handles the clicks at the level of the <table> instead of the rows themselves. That saves a little bit of initialization work.
Similar to the other example, here's how you could do this in pure JavaScript:
// Pure JavaScript:
var table = document.getElementById("table-one");
var rows = table.rows;
var length = rows.length;
for(var i = 0; i < length; i++) {
rows[i].onclick = function() {
alert(this.id);
// Do more stuff with this id.
}
}
In my opinion, this particular problem is solved very nicely by jQuery. If you're performing other operations similar to this, jQuery will save you a ton of time, and keep the complexity level of your code down. Compare the above with this:
// jQuery:
$("#table-one tr").bind("click", function() {
alert(this.id);
});
bind is a jQuery method that just, well, binds an event to a handler :)
Working sample: http://jsfiddle.net/andrewwhitaker/9HEQk/
The easiest way to do that is using jQuery framework.
// Your function
function MyFunc(rowId){
alert(rowId);
}
// Binde click event to all rows with class "ClockableRow"
// within table with class "clickableTable"
$(".clickableTable .clickableRow").click(function(){
// Call you function and pass clicked row ID there
MyFunc($(this).attr("id"));
})
You table shoul look like this:
<table class="clickableTable">
<tr id="row1" class="clickableRow"><td>row 1</td></tr>
<tr id="row2" class="clickableRow"><td>row 2</td></tr>
<tr id="row3" class="clickableRow"><td>row 3</td></tr>
<tr id="row4" class="clickableRow"><td>row 4</td></tr>
<tr id="row5" class="clickableRow"><td>row 5</td></tr>
</table>
example