Merge neighbouring HTML table cells with same value using JS - javascript

I've been wrestling with this for a while. I have a table which is automatically generated based on some JSON data, which may vary. I'd like to merge neighbouring cells in the first column which have the same value, e.g. "fish" and "bird" in this table:
<table>
<tr>
<td>fish</td>
<td>salmon</td>
</tr>
<tr>
<td>fish</td>
<td>cod</td>
</tr>
<tr>
<td>fish</td>
<td>plaice</td>
</tr>
<tr>
<td>bird</td>
<td>robin</td>
</tr>
<tr>
<td>bird</td>
<td>crow</td>
</tr>
</table>
I don't want to use any libraries ideally, just pure JS.
This is what I would like it to look like:
table, tr, td {
border: solid 1px black;
}
<table>
<tr>
<td rowspan="3">fish</td>
<td>salmon</td>
</tr>
<tr>
<td>cod</td>
</tr>
<tr>
<td>plaice</td>
</tr>
<tr>
<td rowspan="2">bird</td>
<td>robin</td>
</tr>
<tr>
<td>crow</td>
</tr>
</table>
I've been finding different ways to identify the different values and their frequency and then change the rowspan to the right number and subsequently deleting the the other cells but these all broke down in differing use cases.
This is what I have so far:
let table = document.querySelector('table');
let rowCount = 1;
for (let i = 0; i < (table.rows.length - 1); i++) {
if (table.rows[i].cells[0].innerHTML === table.rows[i + 1].cells[0].innerHTML) {
rowCount++;
} else if (rowCount !== 1) {
table.rows[i].cells[0].setAttribute('rowspan', rowCount);
for (let j = (i - rowCount + 1); j < rowCount; j++) {
table.rows[j].cells[0].remove();
};
rowCount = 1;
};
};
table, tr, td {
border: solid 1px black;
}
<table>
<tr>
<td>fish</td>
<td>salmon</td>
</tr>
<tr>
<td>fish</td>
<td>cod</td>
</tr>
<tr>
<td>fish</td>
<td>plaice</td>
</tr>
<tr>
<td>bird</td>
<td>robin</td>
</tr>
<tr>
<td>bird</td>
<td>crow</td>
</tr>
</table>
This isn't doing what I want at all but I feel I'm really close! It's trying to count the number of (first column) cells for which the one below has the same value, assigning this number to the rowspan of the last relevant cell and then deleting the subsequent cells before looping back to catch the rest of them. I'd love for my final code to be a variation of this, so can someone show me where I'm going wrong please?

You were indeed pretty close!
A way to simplify quite a bit is to keep a reference to the current "header" cell, i.e. the one you want to increase the rowspan of. That way you don't have to deal with indexes at all, yielding a very straightforward algorithm:
For each row
Set firstCell to the row's first cell
If this is the first row OR firstCell's text is different from headerCell's text
Set headerCell to firstCell
Otherwise
Increase headerCell's rowSpan by 1
Remove firstCell
In JavaScript, it looks like this:
const table = document.querySelector('table');
let headerCell = null;
for (let row of table.rows) {
const firstCell = row.cells[0];
if (headerCell === null || firstCell.innerText !== headerCell.innerText) {
headerCell = firstCell;
} else {
headerCell.rowSpan++;
firstCell.remove();
}
}
table, tr, td {
border: solid 1px black;
}
<table>
<tr>
<td>fish</td>
<td>salmon</td>
</tr>
<tr>
<td>fish</td>
<td>cod</td>
</tr>
<tr>
<td>fish</td>
<td>plaice</td>
</tr>
<tr>
<td>bird</td>
<td>robin</td>
</tr>
<tr>
<td>bird</td>
<td>crow</td>
</tr>
</table>

Related

How to replace <td> values in a table with jQuery (in every Rows)? [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 months ago.
Improve this question
I have a table with multiple columns, one column named: ‘Type’. The values in Type column could be: 1 or 2.
I want to replace the value “1” to “Information” and the value “2” to “Problem” in every row with jQuery, how can I do that?
Here in this demo you'll find a function transformTableData() that takes the table existing in the document and will:
find where is located the field having as header the string "Type";
loop through all its rows and change the value of the corresponding field as the result coming out of the map defined on top. So according to the default map I defined, if the field value is '1' it will be transformed to 'Information' and if the value is '2' it will be transformed to 'Problem';
If there's no corresponding value in the map, the value will be untouched;
The function runs when you click the button on the bottom of the page. Of course the same function could be called on document ready.
function transformTableData(){
const map = {
'1' : 'Information',
'2' : 'Problem',
}
const typeHeaderCell = $('table thead tr th:contains(Type)');
const typeHeaderIndex = $(typeHeaderCell).index();
$('table tbody tr').each((i, row)=>{
const rowCell = $(row).find(`td:nth-child(${typeHeaderIndex+1})`);
const value = rowCell.text();
rowCell.text( map?.[value] );
});
}
table, tr, th, td{
border: solid 1px black;
padding: 1rem;
}
button{
margin-top: 1rem;
padding: 1rem;
font-size: 1.25rem;
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table>
<thead>
<tr>
<th>Column1</th>
<th>Column2</th>
<th>...</th>
<th>Type</th>
<th>...</th>
<th>ColumnN</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
<td></td>
<td>1</td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td>2</td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td>INVALID</td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
<button onclick="transformTableData();">Transform Table Data</button>
There are many ways to achieve something like this. Here is one example. It first looks for the index by comparing the text of each cell in the table header. then it gets all cells in the table body with the index in each table row and replaces the content if it is "1" or "2". There are for sure even shorter or faster methods.
// Find index of column with "Type"
let index = -1;
let th = $('#myTable thead tr th');
for (let i=0; i<th.length; i++) {
if ($(th[i]).text() == 'Type') {
index = i;
break;
}
}
// If index is greater then -1 we found the column
if (index > -1) {
// Get all the table cells in each row at the specific index (need to add +1 to the index)
let td = $('#myTable tbody tr td:nth-child(' + (index+1) + ')');
for (let i=0; i<td.length; i++) {
// Compare content and replace it
if ($(td[i]).text() == '1') {
$(td[i]).text('Information');
}
else if ($(td[i]).text() == '2') {
$(td[i]).text('Problem');
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table id="myTable">
<thead>
<tr>
<th>ID</th>
<th>Type</th>
<th>Name</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>1</td>
<td>John</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>Maria</td>
</tr>
<tr>
<td>2</td>
<td>2</td>
<td>Walter</td>
</tr>
<tr>
<td>3</td>
<td>1</td>
<td>Julia</td>
</tr>
</tbody>
</table>

How do you apply CSS to odd/even visible rows after a javascript function has been used to filter out some rows? (incorrectly flagged as duplicate) [duplicate]

This question already has answers here:
Select odd even child excluding the hidden child
(9 answers)
Closed 3 years ago.
edit: Question has been incorrectly flagged as duplicate, I have tried the proposed solution of the other question but was unable to get it to work. This question has now been answered and the solution posted here works perfectly
I'm having an issue with alternating row colours on a table after using some javascript to hide the filtered rows.
I'm using
tr:nth-child(even)
tr:nth-child(odd)
to alternate the colours of the rows of the table, just to make it easier to track across the rows for the user.
There is a search box at the top of each column that applies the filter function I'm using to filter the table beneath. It changes the the rows that don't match the search boxes content to style.display = "none". The issue with this is that after some rows have been hidden, the remaining rows don't always alternate colours.
this is the filter function i have been using
function Filter(n) {
var input, filter, table, tr, td, i, txtValue;
input = document.getElementById("searchbox" + n);
filter = input.value.toUpperCase();
table = document.getElementById("mytable");
tr = table.getElementsByTagName("tr");
for (i = 0; i < tr.length; i++) {
td = tr[i].getElementsByTagName("td")[n];
if (td) {
txtValue = td.textContent || td.innerText;
if (txtValue.toUpperCase().indexOf(filter) > -1) {
tr[i].style.display = "";
} else {
tr[i].style.display = "none";
}
}
}
}
and this is the css for my table
tr:nth-child(even){
background-color: #F6F6F6
}
tr:nth-child(odd){
background-color: #EEEEEE
}
this is the table ive been using
<table id="mytable">
<tr>
<th>
<div>A</div>
<input id="searchbox0" type="text" onkeyup="Filter(0)" placeholder="Filter..">
</th>
<th>
<div>B</div>
<input id="searchbox1" type="text" onkeyup="Filter(1)" placeholder="Filter..">
</th>
<th>
<div>C</div>
<input id="searchbox2" type="text" onkeyup="Filter(2)" placeholder="Filter..">
</th>
<th>
<div>D</div>
<input id="searchbox3" type="text" onkeyup="Filter(3)" placeholder="Filter..">
</th>
</tr>
<tr onclick="window.location='04718J00065.html'">
<td>04718J00065</td>
<td>2100305513</td>
<td>10</td>
<td>-</td>
</tr>
<tr onclick="window.location='29417J01131.html'">
<td>29417J01131</td>
<td>2100305513</td>
<td>*</td>
<td>-</td>
</tr>
<tr onclick="window.location='07416J01979.html'">
<td>07416J01979</td>
<td>2100029648</td>
<td>*</td>
<td>-</td>
</tr>
<tr onclick="window.location='02518J01169.html'">
<td>02518J01169</td>
<td>2100030939</td>
<td>*</td>
<td>-</td>
</tr>
<tr onclick="window.location='09013J00505.html'">
<td>09013J00505</td>
<td>20865</td>
<td>10</td>
<td>-</td>
</tr>
<tr onclick="window.location='04718J00068.html'">
<td>04718J00068</td>
<td>2100305513</td>
<td>10</td>
<td>-</td>
</tr>
<tr onclick="window.location='09618J05120.html'">
<td>09618J05120</td>
<td>2100305513</td>
<td>10</td>
<td>-</td>
</tr>
<tr onclick="window.location='07416J01973.html'">
<td>07416J01973</td>
<td>20862</td>
<td>10</td>
<td>-</td>
</tr>
<tr onclick="window.location='20614J00424.html'">
<td>20614J00424</td>
<td>20861</td>
<td>10</td>
<td>-</td>
</tr>
<tr onclick="window.location='15713J01020.html'">
<td>15713J01020</td>
<td>20870</td>
<td>30</td>
<td>-</td>
</tr>
<tr onclick="window.location='07714J00746.html'">
<td>07714J00746</td>
<td>20861</td>
<td>10</td>
<td>-</td>
</tr>
<tr onclick="window.location='34414J00495.html'">
<td>34414J00495</td>
<td>30939</td>
<td>30</td>
<td>-</td>
</tr>
<tr onclick="window.location='15713J01016.html'">
<td>15713J01016</td>
<td>20862</td>
<td>30</td>
<td>-</td>
</tr>
<tr onclick="window.location='13414J01282.html'">
<td>13414J01282</td>
<td>20861</td>
<td>10</td>
<td>-</td>
</tr>
<tr onclick="window.location='09013J00489.html'">
<td>09013J00489</td>
<td>20861</td>
<td>10</td>
<td>-</td>
</tr>
<tr onclick="window.location='29818J05034.html'">
<td>29818J05034</td>
<td>2100305513</td>
<td>*</td>
<td>-</td>
</tr>
<tr onclick="window.location='23914J00685.html'">
<td>23914J00685</td>
<td>20870</td>
<td>30</td>
<td>-</td>
</tr>
<tr onclick="window.location='13414J01279.html'">
<td>13414J01279</td>
<td>20861</td>
<td>10</td>
<td>-</td>
</tr>
<tr onclick="window.location='14716J01400.html'">
<td>14716J01400</td>
<td>2100030939</td>
<td>30</td>
<td>-</td>
</tr>
<tr onclick="window.location='07714J00748.html'">
<td>07714J00748</td>
<td>20861</td>
<td>10</td>
<td>-</td>
</tr>
</table>
what I'm looking for is a table of alternating visible row colours no matter what rows have been hidden. After filtering, what i get is some rows of the same colour next to each other where the hidden rows are still contributing towards the odd/even count. I have a feeling it's to do with hidden rows still contributing to the index count but I'm not sure how to get around this.
Any help here would be much appreciated.
Instead of changing the display propriety add/remove a class
tr[i].classList.add("active");
tr[i].classList.remove("active");
So then you can simple change the css like so
tr.active {
display: block;
}
tr {
display: none;
}
tr.active:nth-child(even){
background-color: #F6F6F6
}
tr.active:nth-child(odd){
background-color: #EEEEEE
}
As Stephen P pointed out my original answer was wrong, it look like ntl-child ignores the class, so a suggestion to solve this can be something like the following, for simplicity, as MarsAndBack said you can split this into multiple functions.
function Filter(n) {
var input, filter, table, tr, td, i, txtValue, count;
input = document.getElementById("searchbox" + n);
filter = input.value.toUpperCase();
table = document.getElementById("mytable");
tr = table.getElementsByTagName("tr");
for (i = 0, count = 0; i < tr.length; i++) {
td = tr[i].getElementsByTagName("td")[n];
if (td) {
txtValue = td.textContent || td.innerText;
if (txtValue.toUpperCase().indexOf(filter) > -1) {
tr[i].style.display = "";
if (count++ % 2 == 0) {
tr[i].style.background = "#F6F6F6";
} else {
tr[i].style.background = "#EEEEEE";
}
} else {
tr[i].style.display = "none";
}
}
}
}
tr:nth-child(even){
background-color: #F6F6F6
}
tr:nth-child(odd){
background-color: #EEEEEE
}
<table id="mytable">
<tr>
<th>
<div>A</div>
<input id="searchbox0" type="text" onkeyup="Filter(0)" placeholder="Filter..">
</th>
<th>
<div>B</div>
<input id="searchbox1" type="text" onkeyup="Filter(1)" placeholder="Filter..">
</th>
</tr>
<tr>
<td>04718J00065</td>
<td>2100305513</td>
</tr>
<tr>
<td>29417J01131</td>
<td>2100305513</td>
</tr>
<tr>
<td>07416J01979</td>
<td>2100029648</td>
</tr>
<tr>
<td>02518J01169</td>
<td>2100030939</td>
</tr>
</table>
Give "zebra-striping" its own function:
Split your function up so the act of filtering and "zebra-striping" occur independent of one another; each having it's own function() definition.
Then ultimately as a last step in your overall logic, after all queries and filters are done, you run the zebra-striping function to do it's thing on whatever actual rows exist.

How to set class property for a table row in sql script

I have set markup HTML ON in my pl/sql script. I'm running a select query whose output by default as a table I'm writing to a html file.
I want to highlight a few rows in that table based on the value of a column. For that I'm trying to set a CSS class for those rows.
From CSS, I can only access table's <th> and <td> in general. Kindly suggest how this can be done.
$(function() {
var val = ['X', 'Z'];
for (var i = 0; i < val.length; i++) {
$('table tr td:contains(' + val[i] + ')').each(function() {
$(this).closest('tr').addClass('highlight');
});
}
});
.highlight td {
border: 1px solid blue;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table>
<tr>
<td>A</td>
<td>B</td>
</tr>
<tr>
<td>A</td>
<td>X</td>
</tr>
<tr>
<td>A</td>
<td>V</td>
</tr>
<tr>
<td>X</td>
<td>B</td>
</tr>
<tr>
<td>Z</td>
<td>B</td>
</tr>
</table>
If you have a specific range of values, so as to get tr highlighted, You can use something like this. I don't know how to exactly style the 'tr' to make it highlighted.

Hide a table column with Javascript

I have the following table:
<table id="btt-ranges" cellspacing="0" cellpadding="0" border="0"
<tbody>
<tr>
<th scope="col"> </th>
<th id="Business" scope="col">Type of Business</th>
<th id="Ranges" scope="col"> Ranges</th>
<th scope="col">BTT</th>
</tr>
<tr>
<td>Example</td>
<td>Example</td>
<td>Example</td>
<td>Example</td>
</tr>
<tr>
<td>Example</td>
<td>Example</td>
<td>Example</td>
<td>Example</td>
</tr>
<tr>
<td>Example</td>
<td>Example</td>
<td>Example</td>
<td>Example</td>
</tr>
</tbody>
</table>
What I have to do is hide the last column, but I can't change how the table is right now.
I can use Javascript and so far this is what I tried:
function show_hide_column() {
var tbl = document.getElementById('btt-changes');
var rows = tbl.getElementsByTagName('tr');
for (var row = 0; row < rows.length; row++) {
var cols = rows[row].children;
console.log(1, cols.length);
if (4 >= 0 && 4 < cols.length) {
var cell = cols[4];
console.log(cell, cell.tagName);
if (cell.tagName == 'TD') cell.style.display = 'none';
}
}
}
What can I do without touching the table?
This code selects the col's cells (th and tds), and then hides them (fiddle):
var lastColHeader = Array.prototype.slice.call(document.querySelectorAll('th:last-child', '#btt-ranges'), 0); // get the header cell
var lastColCells = Array.prototype.slice.call(document.querySelectorAll('td:last-child', '#btt-ranges'), 0).concat(lastColHeader); // get the column cells, and add header
lastColCells.forEach(function(cell) { // iterate and hide
cell.style.display = 'none';
});
You don't need to use javascript for this. You can use a CSS selector to hide the last column:
#btt-ranges tr td:last-child { display: none; }
Edit: Just realized you specifically need to do it in javascript. Not sure if there is any way to append a style without touching the table.

Add empty td to rows which have less tds than max

I dynamically add data to table from a Javascript object.
I have a code that ends up something like this:
<table>
<tr>
<td>1</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
</tr>
</table>
I want table borders in each row even if the tds don't exist. So basically for the example in the code I want a 3x3 table with borders.
Is this possible without actually making empty tds in each row?
You can:
Modify the original JS so that it generates colspan attributes (<td colspan="3"> will be as wide as 3 <td>'s; Of course you will lose the grid symmetry.
If your table cells each have the same fixed width, you could use a background-image on the table.
You could wrap up a little script to complete the table, cf:
var table = document.getElementsByTagName('table')[0];
function normalizeTable() {
var trs = table.getElementsByTagName('tr'),
len = trs.length, max = 0, td;
// first we search for the longest table row in terms of # of children
for (var i = len; i--;) {
if (trs[i].children.length > max)
max = trs[i].children.length;
}
// now we can fill the other rows
for (var j = len; j--;) {
while (trs[j].children.length < max) {
td = document.createElement('td');
trs[j].appendChild(td);
}
}
}
normalizeTable();
EDITED
In my opinion, you have to add only a little CSS and insert all TD (empty):
table {
border-style: solid;
border-width: 1px;
border-collapse: separate;
empty-cells: show;
}
td {
border-style: solid;
border-width: 1px;
}
<table>
<tr>
<td>1</td>
<td></td> <!-- empty -->
<td></td> <!-- empty -->
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td></td> <!-- empty -->
</tr>
</table>

Categories

Resources