user5215297
user5215297

Reputation: 81

Grouping table rows based on cell value using jquery/javascript

I have been trying to solve this for days.I would greatly appreciate any help you can give me in solving this problem. I have a table like the following:

ID              Name            Study
1111            Angela          XRAY
2222            Bena            Ultrasound
3333            Luis            CT Scan
1111            Angela          Ultrasound
3333            Luis            XRAY
3333            Luis            LASER

and I want to group them like:

ID              Name            Study
GROUP BY id(1111) 2 hits  "+"
2222            Bena            Ultrasound
GROUP BY id(3333) 3 hits "+"

AND if "+" is clicked then it will expand:

 ID              Name            Study
GROUP BY id(1111) 2 hits  "-"
1111            Angela          XRAY  
1111            Angela          Ultrasound
2222            Bena            Ultrasound
GROUP BY id(3333) 3 hits  "-"
3333            Luis            CT Scan
3333            Luis            Ultrasound
3333            Luis            LASER

There is a demo that I found on stackoverflow(http://jsfiddle.net/FNvsQ/1/) but the only problem I have is I want to include all rows having the same id under a dynamic header like

grouped by id(1111) then the expand/collapse icon (+/-)

var table = $('table')[0];
var rowGroups = {};
//loop through the rows excluding the first row (the header row)
while(table.rows.length > 0){
    var row = table.rows[0];
    var id = $(row.cells[0]).text();
    if(!rowGroups[id]) rowGroups[id] = [];
    if(rowGroups[id].length > 0){
        row.className = 'subrow';
        $(row).slideUp();
    }
    rowGroups[id].push(row);
    table.deleteRow(0);
}
//loop through the row groups to build the new table content
for(var id in rowGroups){
    var group = rowGroups[id];
    for(var j = 0; j < group.length; j++){
        var row = group[j];
        if(group.length > 1 && j == 0) {
            //add + button
            var lastCell = row.cells[row.cells.length - 1];           
            $("<span class='collapsed'>").appendTo(lastCell).click(plusClick);                                         
        }
        table.tBodies[0].appendChild(row);        
    }
}
//function handling button click
function plusClick(e){
    var collapsed = $(this).hasClass('collapsed');
    var fontSize = collapsed ? 14 : 0;
    $(this).closest('tr').nextUntil(':not(.subrow)').slideToggle(400)
           .css('font-size', fontSize);
    $(this).toggleClass('collapsed');        
}

Upvotes: 0

Views: 21753

Answers (2)

RobG
RobG

Reputation: 147453

As a comparison, here's a POJS function that is functionally equivalent in the same amount of code. Doesn't use a large external library though.

It uses the same algorithm of collecting all rows with the same value in the first cell, then modifies the table to insert header rows and group the data rows.

// Group table rows by first cell value. Assume first row is header row
function groupByFirst(table) {

  // Add expand/collapse button
  function addButton(cell) {
    var button = cell.appendChild(document.createElement('button'));
    button.className = 'toggleButton';
    button.textContent = '+';
    button.addEventListener('click', toggleHidden, false);
    return button;
  }

  // Expand/collapse all rows below this one until next header reached
  function toggleHidden(evt) {
    var row = this.parentNode.parentNode.nextSibling;
    
    while (row && !row.classList.contains('groupHeader')) {
      row.classList.toggle('hiddenRow');
      row = row.nextSibling;
    }
  }
  
  // Use tBody to avoid Safari bug (appends rows to table outside tbody)
  var tbody = table.tBodies[0];

  // Get rows as an array, exclude first row
  var rows = Array.from(tbody.rows).slice(1);
  
  // Group rows in object using first cell value
  var groups = rows.reduce(function(groups, row) {
    var val = row.cells[0].textContent;
    
    if (!groups[val]) groups[val] = [];
    
    groups[val].push(row);
    return groups;
  }, Object.create(null));
  
  // Put rows in table with extra header row for each group
  Object.keys(groups).forEach(function(value, i) {

    // Add header row
    var row = tbody.insertRow();
    row.className = 'groupHeader';
    var cell = row.appendChild(document.createElement('td'));
    cell.colSpan = groups[value][0].cells.length;
    cell.appendChild(
      document.createTextNode(
        'Grouped by ' + table.rows[0].cells[0].textContent +
        ' (' + value + ') ' + groups[value].length + ' hits'
      )
    );
    var button = addButton(cell);

    // Put the group's rows in tbody after header
    groups[value].forEach(function(row){tbody.appendChild(row)});
    
    // Call listener to collapse group
    button.click();
  });
}

window.onload = function(){
  groupByFirst(document.getElementById('table1'));
}
table {
  border-collapse: collapse;
  border-left: 1px solid #bbbbbb;
  border-top: 1px solid #bbbbbb;
}
th, td {
  border-right: 1px solid #bbbbbb;
  border-bottom: 1px solid #bbbbbb;
}
.toggleButton {
  float: right;
}
.hiddenRow {
  display: none;
}
<table id="table1">
  <tr id="tr1">
    <th>Parent ID</th>
    <th>Parent Name</th>
    <th>Study</th>
  </tr>
  <tr id="tr2">
    <td>1111</td>
    <td>Angela</td>
    <td>XRAY</td>
  </tr>
  <tr id="tr3">
    <td>2222</td>
    <td>Bena</td>
    <td>Ultrasound</td>
  </tr>
  <tr id="tr4">
    <td>3333</td>
    <td>Luis</td>
    <td>CT Scan</td>
  </tr>
  <tr id="tr5">
    <td>1111</td>
    <td>Angela</td>
    <td>Ultrasound</td>
  </tr>
  <tr id="tr6">
    <td>3333</td>
    <td>Luis</td>
    <td>LCD</td>
  </tr>
  <tr id="tr7">
    <td>3333</td>
    <td>Luis</td>
    <td>LASER</td>
  </tr>
</table>

Upvotes: 4

user5215297
user5215297

Reputation: 81

Here is the answer to my own question.

First id's are added to the table and the rows and there's a small change to the JS:

var table = $('table')[0];
var rowGroups = {};

//loop through the rows excluding the first row (the header row)
while (table.rows.length > 1) {
  var row = table.rows[1];
  var id = $(row.cells[0]).text();

  if (!rowGroups[id]) rowGroups[id] = [];

  if (rowGroups[id].length > 0) {
    row.className = 'subrow';
    $(row).slideUp();
  }
  rowGroups[id].push(row);
  table.deleteRow(1);
}

//loop through the row groups to build the new table content
for (var id in rowGroups) {
  var group = rowGroups[id];

  for (var j = 0; j < group.length; j++) {
    var row = group[j];
    var notSubrow = false;

    if (group.length > 1 && j == 0) {
      //add + button
      var lastCell = row.cells[row.cells.length - 1];
      var rowId = row.id;
      var tableId = table.id;
      notSubrow = true;
      //$("<span class='collapsed'>").appendTo(lastCell).click(plusClick);                                         
    }
    table.tBodies[0].appendChild(row);

    if (notSubrow) {
      $('#' + tableId).find('#' + rowId).attr('class', 'subrow');
      $('#' + tableId).find('#' + rowId).before("<tr class='subrowHeader' style='background:#E6E6FA;border-bottom:1px solid #708AA0 !important'><td colspan='3'>&nbsp;group by&nbsp" + $(row.cells[0]).text() + "&nbsp;(" + group.length + ")" + "<span class='collapsed' onclick='plusClick(this)' style='float:left;display:inline'></td></tr>");
      $('#' + tableId).find('#' + rowId).hide();
    }
  }
}
//function handling button click
function plusClick(e) {
  var collapsed = $(e).hasClass('collapsed');
  var fontSize = collapsed ? 14 : 0;
  $(e).closest('tr').nextUntil(':not(.subrow)').slideToggle('fast').css('font-size', fontSize);
  $(e).toggleClass('collapsed');
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table id="table1">
  <tr id="tr1">
    <th>Parent ID</th>
    <th>Parent Name</th>
    <th>Study</th>
  </tr>
  <tr id="tr2">
    <td>1111</td>
    <td>Angela</td>
    <td>XRAY</td>
  </tr>
  <tr id="tr3">
    <td>2222</td>
    <td>Bena</td>
    <td>Untrasound</td>
  </tr>
  <tr id="tr4">
    <td>3333</td>
    <td>Luis</td>
    <td>CT Scan</td>
  </tr>
  <tr id="tr5">
    <td>1111</td>
    <td>Angela</td>
    <td>Untrasound</td>
  </tr>
  <tr id="tr6">
    <td>3333</td>
    <td>Luis</td>
    <td>LCD</td>
  </tr>
  <tr id="tr7">
    <td>3333</td>
    <td>Luis</td>
    <td>LASER</td>
  </tr>
</table>

*Inorder to test copy and paste the code into http://jsfiddle.net/FNvsQ/1/ & In the Frameworks & Extensions panel, set onLoad to No wrap - in body.

Upvotes: 5

Related Questions