Make table row clickable but not headers only with javascript - javascript

Is there a way to archive the same as described in this post using only javascript and no jquery? The goal is to detect clicks on individual rows, while ignoring clicks on the header.
My approach so far:
document.addEventListener('click', function(e) {
var table = document.getElementById("table01");
var rows = table.getElementsByTagName("tr");
for (i = 0; i < rows.length; i++) {
rows[i].addEventListener('click', function(e) {
console.log('clicked')
})
}
})
<table class="table table-striped" id="table01">
<thead class="thead-green">
<tr>
<th scope="col">A</th>
<th scope="col">B</th>
</tr>
</thead>
<tbody>
<tr>
<td>Item 1</td>
<td>Item 2</td>
<td>Item 3</td>
</tr>
<tr>
<td>Item 1</td>
<td>Item 2</td>
<td>Item 3</td>
</tr>
<tr>
<td>Item 1</td>
<td>Item 2</td>
<td>Item 3</td>
</tr>
</tbody>
</table>

I would avoid attaching event listeners to each individual rows, for two reasons:
If the table is too long, the large number of event listeners will
hurt performance
If the table content is dynamic, newly added rows
will require new listeners to be added.
Instead, you can attach an event listener on the entire table and check if the click target is inside the tbody. (this is also known as the event delegation pattern in JavaScript)
var table = document.getElementById("table01");
table.addEventListener('click', e => {
if (e.target.closest('tbody')) {
console.log(e.target.closest('tr')); // emit the row you just clicked;
}
});
<table class="table table-striped" id="table01">
<thead class="thead-green">
<tr>
<th scope="col">A</th>
<th scope="col">B</th>
</tr>
</thead>
<tbody>
<tr>
<th>Item 1</td>
<td>Item 2</td>
<td>Item 3</td>
</tr>
<tr>
<th>Item 1</td>
<td>Item 2</td>
<td>Item 3</td>
</tr>
<tr>
<th>Item 1</td>
<td>Item 2</td>
<td>Item 3</td>
</tr>
</tbody>
</table>
And even simpler than that, you can attach event listener onto the tbody. This way you can guarantee all clicks come from rows in the table body.
var tableBody = document.getElementById("table01").querySelector('tbody');
tableBody.addEventListener('click', e => {
if (e.target.closest('tr')) {
console.log(e.target.closest('tr')); // emit the row you just clicked;
}
});
<table class="table table-striped" id="table01">
<thead class="thead-green">
<tr>
<th scope="col">A</th>
<th scope="col">B</th>
</tr>
</thead>
<tbody>
<tr>
<th>Item 1</td>
<td>Item 2</td>
<td>Item 3</td>
</tr>
<tr>
<th>Item 1</td>
<td>Item 2</td>
<td>Item 3</td>
</tr>
<tr>
<th>Item 1</td>
<td>Item 2</td>
<td>Item 3</td>
</tr>
</tbody>
</table>
Meanwhile, please consider the accessibility of your desired behavior. How will a keyboard user trigger this kind of click? How will a screen reader user know the rows are actually "clickable"?

Just made little modification to Your javascript. See provided code :
document.addEventListener('click', function(e) {
var table = document.getElementById("table01");
var rows = table.getElementsByTagName("tbody")[0].getElementsByTagName("tr");
for (i = 0; i < rows.length; i++) {
rows[i].addEventListener('click', function(e) {
alert('clicked');
})
}
});
You'll see that modification is made in Your var rows = table.getElementsByTagName("tbody")[0].getElementsByTagName("tr");. By this way You'll get only rows from tbody, not entire table (thead and tfoot) using pure javascript and avoid querySelector too.

Collect all the headers in a variable and check if any of those are the descendants of each row
var headers = document.getElementsByTagName("th");
for (i = 0; i < rows.length; i++) {
var hasHeader = false;
for (j = 0; j < headers.length; j++) {
if(rows[i].contains(headers[j])) {
hasHeader = true;
break;
}
}
if(!hasHeaders) {
rows[i].addEventListener('click', function(e) {
console.log('clicked')
})
}
}

document.querySelectorAll("#table01>tbody>tr").forEach(function(tr){tr.addEventListener....};
">" stands for the direct children.

Related

How to get the cell element that is spanning another cell element in a table using JavaScript

We have a table:
<table>
<tbody>
<tr>
<td>Column 1</td>
<td colspan="3">Column 2</td>
<td>Column 3</td>
<td colspan="99999">Column 4</td>
</tr>
<tr>
<td>A</td>
<td>B</td>
<td id="target">C</td>
<td>D</td>
<td>E</td>
<td>F</td>
</tr>
</tbody>
</table>
Using JavaScript or jQuery, how would we able to get the column element (or its index) of the first row that is spanning the cell with id "target"? I don't really want to use any box positioning method (is: getBoundingClientRect()) technique.
In this example, the associated cell element that is spanning "target" is the cell with text "Column 2".
Here is a solution for the case, that the second row also has colspans and there is no third row:
Iterate over the cells of the second row with a for loop and count their colspans until you find your target cell (if there is no colspan defined it is automatically '1'). Then iterate over the cells of the first row and count their colspans until the count is equal or bigger then the count of the second row. In that case you have found the desired head cell.
Working example:
const head_cells = document.querySelectorAll('#head-row td');
const target_cells = document.querySelectorAll('#target-row td');
let head_position = 0;
let target_position = 0;
for (i = 0; i < target_cells.length; i++) {
target_position += target_cells[i].colSpan;
if (target_cells[i].id === 'target') {
for (k = 0; k < head_cells.length; k++) {
head_position += head_cells[k].colSpan;
if (head_position >= target_position) {
console.log(head_cells[k].textContent);
break;
}
}
break;
}
}
<table>
<tbody>
<tr id="head-row">
<td>Column 1</td>
<td colspan="4">Column 2</td>
<td>Column 3</td>
<td colspan="99999">Column 4</td>
</tr>
<tr id="target-row">
<td>A</td>
<td colspan="2">B</td>
<td id="target">C</td>
<td>D</td>
<td>E</td>
<td>F</td>
</tr>
</tbody>
</table>
<script>
function findHeader(cell) {
let count = cell.cellIndex + 1; // 3
for(let header of headers.cells) {
const colspan = +header.getAttribute('colspan') || 1;
count -= colspan;
if (count<1) return alert(header.textContent);
}
}
</script>
<table border=1>
<tbody>
<tr id="headers">
<td>Column 1</td>
<td colspan="3">Column 2</td>
<td>Column 3</td>
<td colspan="99999">Column 4</td>
</tr>
<tr>
<td>A</td>
<td>B</td>
<td onclick="findHeader(this)">Click</td>
<td>D</td>
<td>E</td>
<td>F</td>
</tr>
</tbody>
</table>

Is there any direct way to get a minimal HTML node structure in jQuery?

This is the input node structure
<table>
<thead>
<tr style="text-align: left;">
<th class="some_class"><div><div><span>COL1</span></div></div></th>
<th class="some_class"><div><div><span>COL2</span></div></div></th>
<th class="some_class"><div><div><span>COL3</span></div></div></th>
<th class="some_class"><div><div><span>COL4</span></div></div></th>
</tr>
</thead>
<tbody>
<tr>
<td>content 1</th>
<td>content 2</td>
<td>content 3</td>
<td>content 4</td>
</tr>
</tbody>
</table>
And this is the wanted output structure
<table>
<thead>
<tr>
<th>COL1</th>
<th>COL2</th>
<th>COL3</th>
<th>COL4</th>
</tr>
</thead>
<tbody>
<tr>
<td>content 1</th>
<td>content 2</td>
<td>content 3</td>
<td>content 4</td>
</tr>
</tbody>
</table>
I could just remove the elements manually with some mappings or some loops, but I am wondering if there is a better way to just get the minimal HTML possible without attributes
Removing the attributes you can use the removeAttr,.
To get rid of <div><div><span>...,. You could loop the th get the text() and then set using text() again this has the effect of getting rid of the the extra tags.
eg..
const c = $('table').clone();
c.find('*').removeAttr('class style');
c.find('th').each(function() { $(this).text($(this).text()); });
console.log(c[0].outerHTML);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table>
<thead>
<tr style="text-align: left;">
<th class="some_class"><div><div><span>COL1</span></div></div></th>
<th class="some_class"><div><div><span>COL2</span></div></div></th>
<th class="some_class"><div><div><span>COL3</span></div></div></th>
<th class="some_class"><div><div><span>COL4</span></div></div></th>
</tr>
</thead>
<tbody>
<tr>
<td>content 1</td>
<td>content 2</td>
<td>content 3</td>
<td>content 4</td>
</tr>
</tbody>
</table>
Sure, it's pretty easy...
Here i have a codesandbox for you where this works:
https://codesandbox.io/s/wonderful-star-g8h8f?file=/index.html
$(".noattrs *").each(function() {
// first copy the attributes to remove
// if we don't do this it causes problems
// iterating over the array we're removing
// elements from
var attributes = $.map(this.attributes, function(item) {
return item.name;
});
// now use jQuery to remove the attributes
var el = $(this);
$.each(attributes, function(i, item) {
el.removeAttr(item);
});
})

Append tbody td to th based on similar index

i'm trying to append tbody td with the similar to thead th with the similar index.
$("table").find("th").each(function(i, e) {
console.log(i, e)
$(this).attr('head-index', i)
});
$("table").find("td").each(function(i, e) {
console.log(i, e)
$(this).attr('row-index', i)
});
var tableTH = $("table > thead > tr > th");
var tableTR = $("table > tbody > tr > td");
if ($(tableTH).attr == $(tableTR).attr) {
} else {
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table class="table table-hover">
<thead>
<tr>
<th>Content 1 Head</th>
<th>Content 2 Head</th>
</tr>
</thead>
<tbody>
<tr>
<td>Content 1</td>
<td>Content 2</td>
</tr>
<tr>
<td>Content 4</td>
<td>Content 5</td>
</tr>
<tr>
<td>Content 7</td>
<td>Content 8</td>
</tr>
</tbody>
</table>
This function will be triggered in mobile view only.
End Result expected.
enter image description here
*Edited(What is there's more TR)
You can use .each() to loop over the headers, and an attribute selector to select get the row with the same index.
$("table").find("th").each(function(i, e) {
$(this).attr('head-index', i)
});
$("table").find("td").each(function(i, e) {
$(this).attr('row-index', i)
});
var tableTH = $("table > thead > tr > th");
var tableTR = $("table > tbody > tr > td");
tableTH.each(function() {
var index = $(this).attr("head-index");
var row = tableTR.filter(`[row-index=${index}]`);
$(this).after(row);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table class="table table-hover">
<thead>
<tr>
<th>Content 1 Head</th>
<th>Content 2 Head</th>
<th>Content 3 Head</th>
</tr>
</thead>
<tbody>
<tr>
<td>Content 1</td>
<td>Content 2</td>
<td>Content 3</td>
</tr>
</tbody>
</table>
I used .after() rather than .append(). You can't append a <td> inside a <th>, they both have to be children of <tr>.
Is this you are trying to achieve?
$(function(){
var th = $("table > thead > tr > th");
var td = $("table > tbody > tr > td");
$('thead tr').each(function(th_index,th_item) {
var index = $(th_item).attr("data-th-index");
var row = $('tbody tr[data-td-index="'+index+'"]');
row.each(function(td_index,td_item){
$(th_item).after(td_item);
});
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table class="table table-hover">
<thead>
<tr data-th-index="1">
<th>Content 1 Head</th>
</tr>
<tr data-th-index="2">
<th >Content 2 Head</th>
</tr>
<tr data-th-index="3">
<th >Content 3 Head</th>
</tr>
</thead>
<tbody>
<tr data-td-index="1">
<td >Content 1</td>
</tr>
<tr data-td-index="2">
<td >Content 2</td>
</tr>
<tr data-td-index="3">
<td >Content 3</td>
</tr>
<tr data-td-index="1">
<td >Content 5</td>
</tr>
<tr data-td-index="2">
<td >Content 6</td>
</tr>
<tr data-td-index="3">
<td >Content 7</td>
</tr>
</tbody>
</table>
Use $.each on all TDs
If index is even number then add first th(Head 1)
If index is odd number then add second th(Head 2)
Append string and create new table
var new_table_string="";
$.each($("table tbody tr td"), function (index,value){
console.log(value);
var tdval= $(this).html()
new_table_string += "<tr><th>" + $("table thead tr th").eq(index%2).html() + "</th></tr><tr><td>" + tdval + "</td></tr>";
});
//$("table").empty();
$(".table2").append(new_table_string);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Before:
<table class="table table-hover" border="1">
<thead>
<tr>
<th>Content 1 Head</th>
<th>Content 2 Head</th>
</tr>
</thead>
<tbody>
<tr>
<td>Content 1</td>
<td>Content 2</td>
</tr>
<tr>
<td>Content 3</td>
<td>Content 4</td>
</tr>
<tr>
<td>Content 5</td>
<td>Content 6</td>
</tr>
<tr>
<td>Content 7</td>
<td>Content 8</td>
</tr>
</tbody>
</table>
After:
<table class="table2" border="1"></table>

How to make table jQuery pagination logic

I am trying to make a jQuery "plugin" to paginate records on a table. According to my own question I tried to make manually.
Also, I am trying to figure out how to display the page numbers correlatively according to the buttonSize.
$(document).ready(function() {
$('#tabla').pagination({
pageSize: 1,
buttonSize: 5,
target: '#paginacion',
template: `<nav class="text-center" aria-label="Page navigation">
<ul class="pagination" id="paginacion">
</ul>
</nav>`
});
});
$.fn.extend({
pagination: function($options) {
$el = this;
buttons = $options.buttonSize || 5;
htmlElement = $($options.template);
target = $(htmlElement).find($options.target);
rows = $($el).find('tbody tr');
initialPage = 1;
pagesAmount = Math.ceil(rows.length / $options.pageSize);
pages = [];
while(rows.length > 0) {
let page = rows.splice(0, $options.pageSize);
pages.push(page);
}
window.pages = pages;
window.currentPage = initialPage;
for(i=0; i<pagesAmount; i++) {
paginationHTML = `<li><a data-page="${i}" href="#">${i+1}</a></li>`;
target.append(paginationHTML);
}
$el.append(htmlElement);
$el.find('tbody').html(pages[window.currentPage-1]);
let scriptCode = `$('${$options.target} a').on('click', function() {` + "\n\t" +
`window.currentPage = $(this).attr('data-page'); ` + "\n\t" +
`$('#${$el.attr('id')} tbody').html(window.pages[window.currentPage]); ` + "\n" +
`$(this).parent().parent().find('li').each(function(item) { $(this).removeClass('active'); })` + "\n" +
`$(this).parent().addClass('active')` + "\n" +
`});` + "\n"
$('body').append($('<script>').html(scriptCode));
}
})
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table id="tabla" class="table table-striped table-bordered">
<thead>
<tr>
<th>Column 1</th>
<th>Column 2</th>
<th>Column 3</th>
<th>Column 4</th>
</tr>
</thead>
<tbody>
<tr>
<td>Column1: Row 1</td>
<td>Column2: Row 1</td>
<td>Column3: Row 1</td>
<td>Column4: Row 1</td>
</tr>
<tr>
<td>Column1: Row 2</td>
<td>Column2: Row 2</td>
<td>Column3: Row 2</td>
<td>Column4: Row 2</td>
</tr>
<tr>
<td>Column1: Row 3</td>
<td>Column2: Row 3</td>
<td>Column3: Row 3</td>
<td>Column4: Row 3</td>
</tr>
<tr>
<td>Column1: Row 4</td>
<td>Column2: Row 4</td>
<td>Column3: Row 4</td>
<td>Column4: Row 4</td>
</tr>
<tr>
<td>Column1: Row 5</td>
<td>Column2: Row 5</td>
<td>Column3: Row 5</td>
<td>Column4: Row 5</td>
</tr>
<tr>
<td>Column1: Row 6</td>
<td>Column2: Row 6</td>
<td>Column3: Row 6</td>
<td>Column4: Row 6</td>
</tr>
<tr>
<td>Column1: Row 7</td>
<td>Column2: Row 7</td>
<td>Column3: Row 7</td>
<td>Column4: Row 7</td>
</tr>
<tr>
<td>Column1: Row 8</td>
<td>Column2: Row 8</td>
<td>Column3: Row 8</td>
<td>Column4: Row 8</td>
</tr>
</tbody>
</table>
I didn't use ES6 functions since our system is Microsoft based and it won't work on IE.
I am not sure how to adjust the button size so if I have two hundred rows it won't display two hundred pages.
Any suggestions?

Sort child row data and move parent along with child group

I have table like this:
<table>
<tr class="parent">
<th id="apple">Apple</th>
<th id="orange">Orange</th>
<th>Banana</th>
</tr>
<tr class="parent">
<td>Apple</td>
<td>Orange</td>
<td>Banana</td>
</tr>
<tr class="child">
<td>Apple 1</td>
<td>Orange 1</td>
<td>Banana 1</td>
</tr>
<tr class="child">
<td>Apple 2</td>
<td>Orange 2</td>
<td>Banana 2</td>
</tr>
<tr class="parent">
<td>Table</td>
<td>cHAIR</td>
<td>Mouse</td>
</tr>
<tr class="child">
<td>Table 1</td>
<td>cHAIR 1</td>
<td>Mouse 1</td>
</tr>
<tr class="child">
<td>Table 2</td>
<td>cHAIR 2</td>
<td>Mouse 2</td>
</tr>
</table>
I want to sort only child row data onclick of <th> and move parent tr along with the child tr. I am unable to do this as it's only one table.
The code I am using currently is: demo of code
I think I now understand what you want. I think you want to sort the groups among each other, by using the parent, and also sort the children within the group.
$('th').click(function() {
//remove and group
var groups = [];
$(this).parent().siblings().each(function() {
$(this).detach();
if($(this).hasClass('parent')) {
groups.push({parent:$(this), children:[]});
} else {
groups[groups.length-1].children.push($(this));
}
});
//sort, inter-group, and intra-group
var i = $(this).index();
function compareRows(rowA, rowB) {
var a = $(rowA.children()[i]).text();
var b = $(rowB.children()[i]).text();
return a < b ? -1 : a > b ? 1 : 0;
}
groups.sort(function(groupA, groupB) {
return compareRows(groupA.parent, groupB.parent);
});
$.each(groups, function(_, group) {
group.children.sort(compareRows);
});
//reinsert
var table = $(this).parent().parent();
$.each(groups, function(_, group) {
table.append(group.parent).append(group.children);
});
});
Please clarify if your intent was other.

Categories

Resources