I have following HTML code for an table:
<div id="table">
<table>
<thead>
<tr>
<th>Product</th>
<th>net</th>
<th class="green">VAT</th>
</tr>
</thead>
<tbody>
<tr>
<td class="red">Lipstick</td>
<td>€6.58</td>
<td>19</td>
</tr>
<tr>
<td class="red">Shoelaces</td>
<td>€7.34</td>
<td>19</td>
</tr>
<tr>
<td class="apple">Apple</td>
<td>0.43</td>
<td>7</td>
</tr>
</tbody>
</table>
</div>
The table is created with tableify.
Now i want to select a row with this function:
function selectedRow(){
var index,
table = document.getElementById("table");
for(var i = 1; i < table.rows.length; i++)
{
table.rows[i].onclick = function()
{
// remove the background from the previous selected row
if(typeof index !== "undefined"){
table.rows[index].classList.toggle("selected");
}
console.log(typeof index);
// get the selected row index
index = this.rowIndex;
// add class selected to the row
this.classList.toggle("selected");
console.log(typeof index);
};
}
}
selectedRow();
Here is the following CSS code so that the the background geht a color:
.selected{
background-color: brown;
color: #fff;
}
Normally should the row, that i selected turn brown, but i get a error, that length can't be read. If i have a normal table without and >tbody>, the code works.
How can i get the body so that can i read the length and the row?
Now that you updated the question you added a parent div to fetch the child table. So the selector in that case should be: #table > table
Here's a code that achieves that same result in a better way and using addEventListener instead of the onclick property.
Two strategies are shown to listen for the click event:
Having a click event handler for each row
Having a click event handler for the whole table
The first one just listen for the click event for any single row in the table and will check if the table has any selected row before attempting to toggle the class or if the clicked row is the one having the selected class.
The second has one listener for the click event on the table only. Since the event bubble starting from the exact child element clicked (a td for example) it will trigger anyway but it needs to make further checks to better target the context.
In the end...
I left the first strategy cabled with the logics and made it more narrow so that you can select any row at any moment and it will become the only selected row in the table
addClickEventToTableRows();
//adds a click event handler to the table
function addClickEventToTable(){
const table = document.querySelector("#table table");
table.addEventListener('click', tableOnClick);
}
//the click event handler for the table
function tableOnClick(event){
const clickedElement = event.target;
const hasTbodyAsParent = clickedElement.closest('tbody') !== null;
//if the child element clicked of the table, is a child of tbody
if ( hasTbodyAsParent ){
//the clickedrow taken with .closest (since clickedElement could be a td)
const clickedRow = clickedElement.closest('tr');
clickedRow.classList.toggle("selected");
clickedRow.closest('table').removeEventListener('click', tableOnClick);
}
}
//adds a click event handler to all the rows of the table
function addClickEventToTableRows(){
//the table element
const table = document.querySelector("#table table");
//foreach row element in the tbody of the currently selected table
table.querySelectorAll(':scope > tbody > tr')
.forEach(row => {
//add a click event listener to the current row
row.addEventListener('click', (event)=>{
//retrieves the clicked table row
const clickedRow = event.currentTarget;
console.log(`Row: ${clickedRow.rowIndex} was clicked`);
//if there's not any row in the table with the 'selected' class OR this row has the class selected
//if(!clickedRow.closest('tbody').querySelector('.selected') || clickedRow.classList.contains('selected'))
table.querySelectorAll(':scope > tbody > tr.selected').forEach(trSelected => {
trSelected.classList.remove('selected');
});
//toggle its selected class
clickedRow.classList.toggle("selected");
});
});
}
#table > table > tbody > tr{
cursor: pointer;
}
#table > table{
border-collapse: collapse;
}
#table > table > tbody td{
border: solid 1px lightgray;
padding: 1rem;
}
#table > table > thead th{
background: gray;
color: white;
font-weight: 600;
font-size: 1.5rem;
padding: .5rem;
}
.selected {
background-color: brown;
color: #fff;
}
<div id="table">
<table>
<thead>
<tr>
<th>Product</th>
<th>net</th>
<th class="green">VAT</th>
</tr>
</thead>
<tbody>
<tr>
<td class="red">Lipstick</td>
<td>€6.58</td>
<td>19</td>
</tr>
<tr>
<td class="red">Shoelaces</td>
<td>€7.34</td>
<td>19</td>
</tr>
<tr>
<td class="apple">Apple</td>
<td>0.43</td>
<td>7</td>
</tr>
</tbody>
</table>
</div>
Related
I'm trying to move an HTML element to a specific location within a table. For example, I have a disabled button labeled "abcd" and a table with cell value of "xyz". I want to move the button "abcd" on top of the cell with the value "xyz" by referencing that value.
So far, the code I have for the javascript function looks like this:
<script type="text/javascript">
function moveObject() {
var label = prompt("Please enter object to move", "");
var location = prompt("Please enter cell location", "");
var element = document.getElementById(label);
}
</script>
How do I reference the cell value so that I can tell the object to move there?
To achieve expected result, use below option using document.getElementsByTagName and prepend to add button to cell value
Get all tds using document.getElementsByTagName('TD')
Loop all td elements using for of
Look for cell with text xyz and prepend disabled button- abcd
function moveObject() {
let tds = document.getElementsByTagName('TD')
let btn = document.getElementById('abcd')
for(let cell of tds){
if(cell.innerHTML ==='xyz'){
cell.prepend(btn)
}
}
}
table tr td{
border: 1px solid black;
}
<table>
<tr>
<td>test</td>
<td>xyz</td>
</tr>
</table>
<button id="abcd" disabled>abcd</button>
<button onclick="moveObject()">Move</button>
Say you have the following structure for a html table:
<table id="myTable">
<tr>
<td>Cell A</td>
<td>Cell B</td>
</tr>
<tr>
<td>Cell C</td>
<td>Cell D</td>
</tr>
</table>
As you can see every cell is a <td> element.
Since our table has an unique id - myTable - we can get all it's TD elements by calling:
var cells=document.getElementById("myTable").getElementsByTagName("td");
The variable cells holds a html collection of all the TD elements of myTable but what we are really interested in is the actual content of a cell - so we need to loop over this array and refer to each cells content using .firstChild.data.
If we compare this to a string we can see which cell matches.
Here's an example:
function getCell(myString) {
var cells = document.getElementById("myTable").getElementsByTagName("td");
for (var a = 0; a < cells.length; a++) {
if (cells[a].firstChild.data == myString) {
return cells[a];
}
}
return null;
}
console.log(getCell("Cell C"));
<table id="myTable">
<tr>
<td>Cell A</td>
<td>Cell B</td>
</tr>
<tr>
<td>Cell C</td>
<td>Cell D</td>
</tr>
</table>
prompt() is a crappy interface. Do this instead:
Wrap everything in a block level element (div, section, main, etc), we'll refer to it as the "parent"
Register the parent to the click event either by on-event property:
parent.onclick = callback
or by Event Listener:
parent.addEventListener('click', callback)
now the parent will detect all clicks on anything within the parent.
Use event.target to determine what was clicked. No id, class, or tagName is needed to identify event.target, but use .matches() to narrow down event.target in if/else control.
function addButton(event) {
const clicked = event.target;
const buttons = this.querySelectorAll('button');
const prompt = this.querySelector('legend');
if (clicked.matches('button')) {
if (clicked.matches('.selected')) {
clicked.classList.remove('selected');
prompt.textContent = 'Click a Button';
prompt.style.color = 'black';
} else {
for (let button of buttons) {
button.classList.remove('selected');
}
clicked.classList.add('selected');
prompt.textContent = 'Click a Cell';
prompt.style.color = 'red';
}
} else if (clicked.matches('td')) {
let button = this.querySelector('.selected');
if (button) {
clicked.appendChild(button);
} else {
return false;
}
} else {
return false;
}
}
document.querySelector('main').onclick = addButton;
table {
table-layout: fixed;
border-spacing: 3px;
width: 100%;
}
th,
td {
width: 33%;
border: 1px solid #000;
padding: 1px
}
th::before,
td::before {
content: '\a0';
}
legend {
font-size: 1.75rem
}
button {
display: inline-block;
margin: 0 5px;
line-height: 100%;
}
.selected {
color: red;
outline: 2px solid red;
}
<main>
<table>
<thead>
<tr><th>A</th><th>B</th><th>C</th></tr>
</thead>
<tbody>
<tr><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td></tr>
</tbody>
</table>
<fieldset>
<legend>Click a Button</legend>
<button>X</button>
<button>X</button>
<button>X</button>
<button>X</button>
</fieldset>
</main>
I am using PHP and MySQL to build an HTML table. I am trying to use JavaScript to filter/search the table and only display the rows with the results I need. I want the JavaScript input to search multiple <td>s of the table. I was able to get this to work, but it is not going to be an elegant solution to put in place with larger tables.
I am sure there is a better way to choose what is being searched, but have not been able to find anything. Does anybody know a way for me to make this code more flexible for varying column width tables?
function myFunction() {
var input, filter, table, tr, td, i;
input = document.getElementById("myInput");
filter = input.value.toUpperCase();
table = document.getElementById("myTable");
tr = table.getElementsByTagName("tr");
for (i = 0; i < tr.length; i++) {
td = tr[i].getElementsByTagName("td")[0];
td1 = tr[i].getElementsByTagName("td")[1];
if (td+td1) {
if ((td.innerHTML.toUpperCase().indexOf(filter)+td1.innerHTML.toUpperCase().indexOf(filter)) > -2) {
tr[i].style.display = "";
} else {
tr[i].style.display = "none";
}
}
}
}
<input type="text" id="myInput" onkeyup="myFunction()" placeholder="Search" title="Type in anything">
<table id="myTable">
<tr class="header">
<th style="width:60%;">Name</th>
<th style="width:40%;">Country</th>
</tr>
<tr>
<td>North/South</td>
<td>UK</td>
</tr>
<tr>
<td>Paris specialites</td>
<td>France</td>
</tr>
</table>
There's a lot you can improve. Start by remembering to explicitly declare your variables, otherwise they become global.
This solution doesn't rely on any specific number of columns. It will work no matter how many there are.
See comments inline for more:
// Get DOM references just once:
var input = document.getElementById("myInput");
var table = document.getElementById("myTable");
// Do event binding in JavaScript, not HTML
input.addEventListener("keyup", filter);
input.addEventListener("search", filter);
// Get all rows, except the header and convert to array so .forEach() can be used to loop
var rows = Array.prototype.slice.call(table.querySelectorAll("tr:not(.header)"));
function filter() {
// Always trim user input
var filter = input.value.trim().toUpperCase();
// Loop the rows
rows.forEach(function(row) {
// You really don't need to know if the search criteria
// is in the first or second cell. You only need to know
// if it is in the row.
var data = "";
// Loop over all the cells in the current row and concatenate their text
Array.prototype.slice.call(row.getElementsByTagName("td")).forEach(function(r){
// Don't use .innerHTML unless there is HTML. Use textContent when there isn't.
data += r.textContent;
});
// Check the string for a match and show/hide row as needed
// Don't set individual styles. Add/remove classes instead
if(data.toUpperCase().indexOf(filter) > -1){
// show row
row.classList.remove("hidden");
} else {
// hide row
row.classList.add("hidden");
}
});
}
input[type=search]{
border-radius:10px;
outline:0;
padding:3px;
}
input[type=search]:focus{
box-shadow:0 0 4px blue;
}
.hidden { display:none; }
.leftHeader { width:60%; }
.rightHeader { width:40%; }
<!-- Use the actual "search" input type and don't do inline CSS or JavaScript -->
<input type="search" id="myInput" placeholder="Search" title="Type in anything">
<table id="myTable">
<tr class="header">
<th class="leftHeader">Name</th>
<th class="rightHeader">Country</th>
</tr>
<tr>
<td>North/South</td>
<td>UK</td>
</tr>
<tr>
<td>Paris specialites</td>
<td>France</td>
</tr>
</table>
All what you have to do is to get the td content and then match it with the search input.
function search(value) {
$('table tr').each(function () {
var content = $(this).find('td').text();
if (content.toUpperCase().includes(value.trim().toUpperCase())) {
$(this).show();
} else {
$(this).hide();
}
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<!DOCTYPE html>
<html>
<body>
<input type="search" placeholder="Search..." id="search_account" onkeyup="search(this.value)">
<table>
<tr>
<td>Cell1</td>
<td>Cell2</td>
<td>Cell3</td>
</tr>
<tr>
<td>Cell4</td>
<td>Cell5</td>
<td>Cell6</td>
</tr>
</table>
</body>
</html>
JSFiddle.
In the following SSCCE, there is a <table> nested inside another <table>.
The question is about the click listener for #add button. Specifically, the last if/else block of the function. When you run this code, click the Add TextField button once (or more times), and you will see that the #remove button on which show() should be executed, is only shown for the first matched selector, and not both (or all) of them.
Ideally the Remove TextField should be shown for all the #remove selectors.
The question is why? How do I fix this?
$(document).on("click", "button#add", function() {
event.preventDefault();
var parentTable = $(this).parent().parent().parent().parent().parent().parent().parent().parent();
var lastTableRow = parentTable.children('tbody').children('tr:last');
//Adding the new row
parentTable.children('tbody').append(lastTableRow.clone());
//Reset lastRow variable
lastTableRow = parentTable.children('tbody').children('tr:last');
//Reset the fields
lastTableRow.find('table tbody tr td input').each(function() {
$(this).val('');
});
//update numberOfRows variable
var numberOfRows = parentTable.children('tbody').children('tr').length;
alert("numberOfRows:" + numberOfRows); //check
if (!(numberOfRows > 1)) {
$("#remove").hide();
} else {
$("#remove").show();
}
});
#outer-table {
padding: 20px;
border: 3px solid pink;
}
#inner-table {
border: 3px solid orange;
}
#remove {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table id="outer-table">
<tbody>
<tr>
<td>
<table id="inner-table">
<tbody>
<tr>
<td>
<p style="display:inline-block">Enter first complain:</p>
<input type="text" />
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>
<button id="add">Add Textfield</button>
<button id="remove">Remove Textfield</button>
</td>
</tr>
</tfoot>
</table>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>Table Footer</td>
</tr>
</tfoot>
</table>
That's because you're using id for a group of objects. id should be unique per document. You should use a class name instead.
After hitting ADD button I get yet another row. I need it's row class should be changed accordingly by identifying previous row's class. If previous row class name is odd then newly created class name should be even and so on. How do we do this by using javascript? How can we modify existing code to do this or any other suggestion?
Proper CSS file is ready for alternate class names. When I fetch data from database then alternate odd/even row will be created. But when I hit ADD button it always take first rows class name because there is a clone of first row always. I have no worries for cloned row except class name.
<script>
function addRow(tableID) {
var table = document.getElementById(tableID);
if (!table) return;
var newRow = table.rows[1].cloneNode(true);
// Now get the inputs and modify their names
var inputs = newRow.getElementsByTagName('input');
for (var i=0, iLen=inputs.length; i<iLen; i++) {
// Update inputs[i]
}
// Add the new row to the tBody (required for IE)
var tBody = table.tBodies[0];
tBody.insertBefore(newRow, tBody.lastChild);
}
</script>
<table id="table1" border=1 class="display">
<tr class="odd">
<th>Operator ID</th>
<th>Status</th>
</tr>
<tr class="odd">
<td>TestA</td>
<td>ActiveA</td>
</tr>
<tr class="even">
<td>TestB</td>
<td>ActiveB</td>
</tr>
</table>
<input type="button" value="ADDROW" onclick=addRow("table1"); />
Why not just use the proper CSS:
#table1 tr {
/* odd row styles */
/* also acts as fallback for really old browsers */
}
#table1 tr:nth-child(even) {
/* even row styles */
}
You can simply do something like this:
tr:nth-child(even) td {
background-color: #ccc;
}
tr:nth-child(odd) td {
background-color: #fff;
}
DEMO HERE
I have a bunch of <tr> some of them contain a <td> that has class="myClass" but some don't. So it looks like something like this.
<tr>
<td class="myClass"></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
</tr>
If I'm at a <tr>, how do I go up in rows until I hit the closest row that contains a td.myClass? Is there a clever way to do this? What I have now is a while loop that checks prev() and if it finds .myClass, it breaks.
$currentTr.prevAll(':has(td.myClass)').first()
Here's a working example, not so great, but works. http://jsfiddle.net/H2k8m/2/
1) The td with class "color" will be the selected ones.
2) Either you can use that or you can directly assign the selected object to some variable and use it outside the function.
HTML :
<table>
<tr>
<td class="myClass">Hi</td>
<td>world</td>
</tr>
<tr>
<td class="myClass">1</td>
<td class="myClass" >2</td>
</tr>
<tr>
<td>3</td>
<td>4</td>
</tr>
<tr>
<td>5</td>
<td>6</td>
</tr>
</table>
CSS :
.color {
background: #000;
color: #fff;
}
table {
cursor: pointer;
}
tr, td {
min-width: 50px;
border: 1px #000 solid;
padding: 5px;
text-align: center;
}
Jquery :
$(document).ready(function()
{
$("table tr td").click(function() {
if( $(this).prevAll('[class="myClass"]').length <= 0 ) {
var parents = $(this).parent().siblings();
for( i = $(this).parent().index(); i >= 0; i-- ) {
parents.eq( i ).find(".myClass").last().addClass("color");
if( parents.eq( i ).find(".myClass").length > 0) {
break;
}
}
}
else {
$(this).prevAll('[class="myClass"]').first().addClass("color");
}
});
});
Here's another approach that will find the actual closest td with the myClass classname. If you want the tr, you can simply get the parent of what it finds:
var td$ = $("#myTable td.myClass");
$("#myTable td").click(function() {
td$.removeClass("found");
var temp$ = td$.add(this);
var i = temp$.index(this);
if (i > 0) {
temp$.eq(i - 1).addClass("found");
}
});
This gets a list of all td's with myClass. It then adds the clicked on element to that jQuery object (jQuery will sort it into DOM order after adding it). It then finds the index of the clicked on element in that jQuery object and if it's not the first item, it just gets the item before it which will be the closest td.myClass object before it in the table.
Working jsFiddle: http://jsfiddle.net/jfriend00/XqLzb/