3

I am using a normal table, here is my requirement:

If a table row values duplicate it should get merged using javascript.For eg: table 1 row values and table2 row values are same then it should get merged and the value should be displayed only once using javascript. Here I will attach my codings.

function myFunction() {
  const firstRows = { };
  let shade = false;
  
  const colsToMerge = [0, 1];

  Array.from(document.querySelectorAll('tbody tr')).forEach(tr => {
    const text = tr.children[0].innerText;
    if (!(text in firstRows)) {
      firstRows[text] = { shade, elem: tr, count: 1 };
      shade = !shade;
    } else {
      const firstRow = firstRows[text]
      firstRow.count++;
      colsToMerge.forEach(i => tr.children[i].remove());
      colsToMerge.forEach(i =>
        firstRow.elem.children[i]
                .setAttribute('rowspan', firstRow.count)
      );
    }
    if (firstRows[text].shade) tr.classList.add('dark');
  });
}
table {
  font-family: arial, sans-serif;
  border-collapse: collapse;
  width: 100%;
}

th {
  background: #a9a9a9;
}

td,
th {
  border: 1px solid #dddddd;
  text-align: center;
  padding: 8px;
  font-family: monospace;
  font-size: 17px;
}

.dark {
  background-color: #dddddd;
}
<body onload="myFunction()">
  <table>
    <tr>
      <th>Company</th>
      <th>Contact</th>
      <th>Country</th>
    </tr>
    <tbody>
      <tr>
        <td>Alfreds Futterkiste</td>
        <td>Maria Anders</td>
        <td>Germanyindgo</td>
      </tr>
      <tr>
        <td>Alfreds Futterkiste</td>
        <td>Maria Anders</td>
        <td>Germany</td>
      </tr>
      <tr>
        <td>Centro comercial Moctezuma</td>
        <td>Francisco Chang</td>
        <td>Mexico</td>
      </tr>
      <tr>
        <td>Ernst Handel</td>
        <td>Roland Mendel</td>
        <td>Austria</td>
      </tr>
      <tr>
        <td>Island Trading</td>
        <td>Helen Bennett</td>
        <td>UK</td>
      </tr>
      <tr>
        <td>Laughing Bacchus Winecellars</td>
        <td>Yoshi Tannamuri</td>
        <td>Canada</td>
      </tr>
      <tr>
        <td>Magazzini Alimentari Riuniti</td>
        <td>Giovanni Rovelli</td>
        <td>Italy</td>
      </tr>
    </tbody>
  </table>
</body>

Check this table

3
  • What is the issue here? It looks like this duplicate Alfreds is already removed? Commented Feb 8, 2019 at 11:16
  • You can use add attribute "rowspan" in your cells to merge similiar rows. Just like that: jsfiddle.net/two8kq1n Commented Feb 8, 2019 at 11:16
  • @adiga;@Marcelo Macedo I want to set the rowspan dynamically. For eg: Let's take the first two rows if the first column is the same it should get merged dynamically instead of giving it as static. Commented Feb 8, 2019 at 11:20

3 Answers 3

3

Here is a working solution for dynamically merging consecutive row cells in every column.

The solution also works for toggling the shade and will only toggle the shade when a 'full' row starts, which is, when there is no merging between consecutive rows.

Also, you have to wrap the tr in the table header in a thead as that can cause problems with document.querySelectorAll('tbody tr') event tbody is specified in the query.

I slightly modified the table to show the shading and merging: enter image description here

function myFunction() {
  const previousRow = {};
  const colsChanged = {};
  let dark = false;

  Array.from(document.querySelectorAll('tbody tr')).forEach((tr, rowIdx) => {
    Array.from(tr.children).forEach((td, colIdx) => {
      if (rowIdx > 0 && previousRow[colIdx].text === td.innerText) {
        previousRow[colIdx].elem.setAttribute('rowspan', ++previousRow[colIdx].span);
        colsChanged[colIdx] = false;
        td.remove();
      } else {
        previousRow[colIdx] = { span: 1, text: td.innerText, elem: td, dark };
        colsChanged[colIdx] = true;
      }
    });
    const rowChanged = Object.values(colsChanged).every(Boolean);
    dark = rowChanged && rowIdx > 0 ? !dark : dark;
    if (dark) {
      tr.classList.add('dark');
    }
  });
}
table {
  font-family: arial, sans-serif;
  border-collapse: collapse;
  width: 100%;
}

th {
  background: #a9a9a9;
}

td,
th {
  border: 1px solid black;
  text-align: center;
  padding: 8px;
  font-family: monospace;
  font-size: 17px;
}

.dark {
  background-color: #dddddd;
}
<body onload="myFunction()">
  <table>
    <thead>
      <tr>
        <th>Company</th>
        <th>Contact</th>
        <th>Country</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Alfreds Futterkiste</td>
        <td>Maria Anders</td>
        <td>Germanyindgo</td>
      </tr>
      <tr>
        <td>Alfreds Futterkiste</td>
        <td>Maria Anders</td>
        <td>Germany</td>
      </tr>
      <tr>
        <td>Centro comercial Moctezuma</td>
        <td>Francisco Chang</td>
        <td>Mexico</td>
      </tr>
      <tr>
        <td>Ernst Handel</td>
        <td>Francisco Chang</td>
        <td>Austria</td>
      </tr>
      <tr>
        <td>Ernst Handel</td>
        <td>Helen Bennett</td>
        <td>UK</td>
      </tr>
      <tr>
        <td>Laughing Bacchus Winecellars</td>
        <td>Yoshi Tannamuri</td>
        <td>Canada</td>
      </tr>
      <tr>
        <td>Magazzini Alimentari Riuniti 1</td>
        <td>Giovanni Rovelli 1</td>
        <td>Italy</td>
      </tr>
      <tr>
        <td>Magazzini Alimentari Riuniti 2</td>
        <td>Giovanni Rovelli 2</td>
        <td>Italy</td>
      </tr>
      <tr>
        <td>Magazzini Alimentari Riuniti 3</td>
        <td>Giovanni Rovelli 3</td>
        <td>Italy</td>
      </tr>
    </tbody>
  </table>
</body>

According to the comments below, if you want to merge cells to the right only if the corresponding cells were merged in the first column, here is a modification to do it.

a leftMerged variable is used to keep track of whether the first cell on the current line was merged to the previous one, and if so, this enables us to merge the following cells to the right if necessary. The variable is reset to false at the beginning of each line.

enter image description here

function myFunction() {
  const previousRow = {};
  const colsChanged = {};
  let leftMerged = false;
  let dark = false;

  Array.from(document.querySelectorAll('tbody tr')).forEach((tr, rowIdx) => {
    Array.from(tr.children).forEach((td, colIdx) => {
      if (rowIdx > 0 && (colIdx === 0 || leftMerged) && previousRow[colIdx].text === td.innerText) {
        previousRow[colIdx].elem.setAttribute('rowspan', ++previousRow[colIdx].span);
        colsChanged[colIdx] = false;
        td.remove();
        if (colIdx === 0) {
          leftMerged = true;
        }
      } else {
        previousRow[colIdx] = { span: 1, text: td.innerText, elem: td, dark };
        colsChanged[colIdx] = true;
      }
    });
    const rowChanged = Object.values(colsChanged).every(Boolean);
    dark = rowChanged && rowIdx > 0 ? !dark : dark;
    if (dark) {
      tr.classList.add('dark');
    }
    leftMerged = false;
  });
}
table {
  font-family: arial, sans-serif;
  border-collapse: collapse;
  width: 100%;
}

th {
  background: #a9a9a9;
}

td,
th {
  border: 1px solid black;
  text-align: center;
  padding: 8px;
  font-family: monospace;
  font-size: 17px;
}

.dark {
  background-color: #dddddd;
}
<body onload="myFunction()">
  <table>
    <thead>
      <tr>
        <th>Company</th>
        <th>Contact</th>
        <th>Country</th>
        <th>Col4</th>
        <th>Col5</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Alfreds Futterkiste</td>
        <td>Maria Anders</td>
        <td>Germanyindgo</td>
        <td>1</td>
        <td>2</td>
      </tr>
      <tr>
        <td>Alfreds Futterkiste</td>
        <td>Maria Anders</td>
        <td>Germany</td>
        <td>1</td>
        <td>2</td>
      </tr>
      <tr>
        <td>Centro comercial Moctezuma</td>
        <td>Francisco Chang</td>
        <td>Mexico</td>
        <td>1</td>
        <td>2</td>
      </tr>
      <tr>
        <td>Ernst Handel</td>
        <td>Francisco Chang</td>
        <td>Austria</td>
        <td>1</td>
        <td>2</td>
      </tr>
      <tr>
        <td>Ernst Handel</td>
        <td>Helen Bennett</td>
        <td>UK</td>
        <td>1</td>
        <td>2</td>
      </tr>
      <tr>
        <td>Laughing Bacchus Winecellars</td>
        <td>Yoshi Tannamuri</td>
        <td>Canada</td>
        <td>1</td>
        <td>2</td>
      </tr>
      <tr>
        <td>Magazzini Alimentari Riuniti 1</td>
        <td>Giovanni Rovelli 1</td>
        <td>Italy</td>
        <td>1</td>
        <td>2</td>
      </tr>
      <tr>
        <td>Magazzini Alimentari Riuniti 1</td>
        <td>Giovanni Rovelli 2</td>
        <td>Italy</td>
        <td>1</td>
        <td>2</td>
      </tr>
      <tr>
        <td>Magazzini Alimentari Riuniti 3</td>
        <td>Giovanni Rovelli 3</td>
        <td>Italy</td>
        <td>1</td>
        <td>2</td>
      </tr>
    </tbody>
  </table>
</body>

Sign up to request clarification or add additional context in comments.

13 Comments

jo_va i want to merge for first three columns
i want to merge for first three columns if first column is same
@Thirshal, I am sorry, I don't understand what you mean, in your question you want to merge similar cells across rows, which is what this code does, as well as managing the shading. Can you explain me again what you want?
@Thirshal, I fixed the second snippet, I had an error with leftMerged = colIdx === 0, I wrapped it in a if statement to only set it true for the first column, instead of reassigning it
Will post that @jo_va
|
1

In below snippet first I read data from table to js array, then merge rows with similar first column, and generate new html content of table body

let readTable = (tbody) => [...tbody.rows].map(row=>[...row.cells].map(c=>c.innerText));    

function mergeRows(data) {
  let h={};
  data.forEach((r,i)=> { // merge by first column
      let p=h[r[0]];
      if(p) { p[1].push(r[1]); p[2].push(r[2]) };
      if(!p) { h[r[0]]=[r[0],[r[1]],[r[2]],i]}         
  });     
  let result=Object.values(h).sort((a,b)=>a[3]-b[3]);  
  result.forEach(r=> {r[1]=[... new Set(r[1])]; r[2]=[... new Set(r[2])]}); // remove duplicates in seconond and third column
  return result;
}

function genTable(rows, tbody) {    
  let b="",x="";
  rows.forEach(r=> {
    x=r.map((c,i) => `<td>${c}</td>`).slice(0,3);
    b+=`<tr>${x.join('')}</tr>`;
  })
  tbody.innerHTML=b;
  console.log({b,rows, tbody});
}

function myFunction() {
  let table=document.querySelector('table');
  let tbody=table.tBodies[table.tBodies.length-1]; 
  genTable( mergeRows(readTable(tbody)), tbody );
}
table {
  font-family: arial, sans-serif;
  border-collapse: collapse;
  width: 100%;
}

th {
  background: #a9a9a9;
}

td,
th {
  border: 1px solid #dddddd;
  text-align: center;
  padding: 8px;
  font-family: monospace;
  font-size: 17px;
}

.dark {
  background-color: #dddddd;
}
<body onload="myFunction()">
  <table>
    <tr>
      <th>Company</th>
      <th>Contact</th>
      <th>Country</th>
    </tr>
    <tbody>
      <tr>
        <td>Alfreds Futterkiste</td>
        <td>Maria Anders</td>
        <td>Germanyindgo</td>
      </tr>
      <tr>
        <td>Alfreds Futterkiste</td>
        <td>Maria Anders</td>
        <td>Germany</td>
      </tr>
      <tr>
        <td>Centro comercial Moctezuma</td>
        <td>Francisco Chang</td>
        <td>Mexico</td>
      </tr>
      <tr>
        <td>Ernst Handel</td>
        <td>Roland Mendel</td>
        <td>Austria</td>
      </tr>
      <tr>
        <td>Island Trading</td>
        <td>Helen Bennett</td>
        <td>UK</td>
      </tr>
      <tr>
        <td>Laughing Bacchus Winecellars</td>
        <td>Yoshi Tannamuri</td>
        <td>Canada</td>
      </tr>
      <tr>
        <td>Magazzini Alimentari Riuniti</td>
        <td>Giovanni Rovelli</td>
        <td>Italy</td>
      </tr>
    </tbody>
  </table>
</body>

2 Comments

If the first row and second-row first column is same then the column alone should merge, and if first row and second row is different , but the 3 column alone is same then it should not get merged.For eg: In first row and second row first column is "Alfreds" then the column alone should merge. If the first row and second-row first column is "alfred" and "centro" then it should not get merged, even though its third column is same.
@Thirshal I made test - and It works as you describe - if last column has similar values for many rows, then that rows wil NOT merged. Rows will merged only if FIRST column values are the same.
1

you can do this dynamically by Javascript. Check this fiddle:

let previousObj = [];
const rows = $("table tr");
rows.each(function(i, el) {
    let obj = [];
    $(el).children("td").each(function(ic, elc) {
    obj.push(elc);

    if (previousObj.length > ic) {
        if (previousObj[ic].innerHTML == obj[ic].innerHTML) {
        $(previousObj[ic]).attr('rowspan', getRowsSpan(ic, i, obj[ic].innerHTML));
        $(obj[ic]).remove();
      }      
    }
  });

  previousObj = obj;
});

function getRowsSpan(col, row, value) {
    var rowSpan = 2;
  var actualRow = row+1;

  while ($(rows[actualRow]).children("td")[col].innerHTML == value) {
    rowSpan++;
    actualRow++;
  } 

  return rowSpan;
}

https://jsfiddle.net/9e1bcprx/2/

3 Comments

Do you want this without jQuery?
The rowspan could potentially be greater than 2
Sorry, for this bug. I created a function to search the rowSpan, here: jsfiddle.net/9e1bcprx/2

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.