lalengua
lalengua

Reputation: 529

HTML table sortable by ROW instead of COLUMN with JavaScript?

I'm using this code from W3Schools for sorting all table rows by clicking on column header. As expected, this orders all rows by that col value.

I'm shure this has already been answered somewere but I't has been difficult for me to filter serarch results to find a similar way (hopefully in pure javascript) of sorting columns by clicking on a first column row.

That should order all columns by that clicked row values.

I'm hoping for some combined method that could order by column AND by row depending on user click, but just a Sort by Row method would be OK.

Thanks in advace!

Expected behavior

Unsorted table:

<table id="myTable">
  <tbody>
    <tr>
      <th onclick="sortTableRows(0)">Name</th>
      <th onclick="sortTableRows(1)">Col 1</th>
      <th onclick="sortTableRows(2)">Col 2</th>
      <th onclick="sortTableRows(3)">Col 3</th>
    </tr>
    <tr>
      <td onclick="sortTableCols(1)">Alan Brado</td>
      <td>2</td>
      <td>3</td>
      <td>1</td>
    </tr>
    <tr>
      <td onclick="sortTableCols(2)">Kevin Chuca</td>
      <td>1</td>
      <td>3</td>
      <td>2</td>
    </tr>
    <tr>
      <td onclick="sortTableCols(3)">Pamela Chu</td>
      <td>3</td>
      <td>2</td>
      <td>1</td>
    </tr> 
  </tbody>
</table>

NORMAL: Sorted table by COLUMN (Col 1):

<table id="myTable">
  <tbody>
    <tr>
      <th onclick="sortTableRows(0)">Name</th>
      <th onclick="sortTableRows(1)">*Col 1*</th>
      <th onclick="sortTableRows(2)">Col 2</th>
      <th onclick="sortTableRows(3)">Col 3</th>
    </tr>
    <tr>
      <td onclick="sortTableCols(1)">Kevin Chuca</td>
      <td>1</td>
      <td>3</td>
      <td>2</td>
    </tr>
    <tr>
      <td onclick="sortTableCols(1)">Alan Brado</td>
      <td>2</td>
      <td>3</td>
      <td>1</td>
    </tr>
    <tr>
      <td onclick="sortTableCols(3)">Pamela Chu</td>
      <td>3</td>
      <td>2</td>
      <td>1</td>
    </tr> 
  </tbody>
</table>

DESIRED: Sorted table by ROW (Alan Brado):

<table id="myTable">
  <tbody>
    <tr>
      <th onclick="sortTableRows(0)">Name</th>
      <th onclick="sortTableRows(1)">Col 3</th>
      <th onclick="sortTableRows(2)">Col 1</th>
      <th onclick="sortTableRows(3)">Col 2</th>
    </tr>
    <tr>
      <td onclick="sortTableCols(1)">*Alan Brado*</td>
      <td>1</td>
      <td>2</td>
      <td>3</td>
    </tr>
    <tr>
      <td onclick="sortTableCols(2)">Kevin Chuca</td>
      <td>2</td>
      <td>1</td>
      <td>3</td>
    </tr>
    <tr>
      <td onclick="sortTableCols(3)">Pamela Chu</td>
      <td>1</td>
      <td>3</td>
      <td>2</td>
    </tr> 
  </tbody>
</table>

And here is my snippet:

function sortTableRows(n) {
  var table, rows, switching, i, x, y, shouldSwitch, dir, switchcount = 0;
  table = document.getElementById("myTable");
  switching = true;
  dir = "asc"; 
  while (switching) {
    switching = false;
    rows = table.getElementsByTagName("TR");
    for (i = 1; i < (rows.length - 1); i++) {
      shouldSwitch = false;
      x = rows[i].getElementsByTagName("TD")[n];
      y = rows[i + 1].getElementsByTagName("TD")[n];
      if (dir == "asc") {
        if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) {
          shouldSwitch= true;
          break;
        }
      } else if (dir == "desc") {
        if (x.innerHTML.toLowerCase() < y.innerHTML.toLowerCase()) {
          shouldSwitch= true;
          break;
        }
      }
    }
    if (shouldSwitch) {
      rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
      switching = true;
      switchcount ++;      
    } else {
      if (switchcount == 0 && dir == "asc") {
        dir = "desc";
        switching = true;
      }
    }
  }
}
function sortTableCols(n) {
  alert("This should sort cols by row #"+n+" values");
}
  /* Just for beauty */
  #myTable {
    border: none;
  }
  #myTable th {
    cursor: pointer;
    width: 5%;
    border: none;
    background-color: #e0e0e0;
  }
  #myTable tr td {
    border: none;
    border-bottom: 1px solid #aaa;
    text-align: center;
  }
  #myTable tr td:first-child {
    cursor: pointer;
    background-color: #e0e0e0;
    border-bottom: 1px solid #aaa;
    text-align: center;
  }
  #myTable tr:last-child td {
    border: none;
  }
<table id="myTable">
  <tbody>
    <tr>
      <th onclick="sortTableRows(0)">Name</th>
      <th onclick="sortTableRows(1)">Col 1</th>
      <th onclick="sortTableRows(2)">Col 2</th>
      <th onclick="sortTableRows(3)">Col 3</th>
    </tr>
    <tr>
      <td onclick="sortTableCols(1)">Alan Brado</td>
      <td>2</td>
      <td>3</td>
      <td>1</td>
    </tr>
    <tr>
      <td onclick="sortTableCols(2)">Kevin Chuca</td>
      <td>1</td>
      <td>3</td>
      <td>2</td>
    </tr>
    <tr>
      <td onclick="sortTableCols(3)">Pamela Chu</td>
      <td>3</td>
      <td>2</td>
      <td>1</td>
    </tr> 
  </tbody>
</table>

Upvotes: 1

Views: 1019

Answers (2)

lalengua
lalengua

Reputation: 529

Update: See @sanxofon's answer. Is an improvement of mine.


No answers for the time being so I ended up solving the issue using this procedure:

  1. Capturing table to matrix

  2. Transpose matrix (flip axis)

  3. Sort matrix by column (as normally)

  4. Transpose matrix back (flip axis again)

  5. Update matrix to table

I post this for any who lands here that may be interested in a solution.

Performance: I haven't tested it for performance, but as all process are done on matrix it depends entirely on the length of it and of its data. Works quite good and can be implemented on any table.

ToDo: Could be improved by adding listeners for row and column headers and removing onClick="sortTable(x,y)" from table.

Here is it:

var dirc = 0, dirr = 0, cc = 0, rr = 0;
function colSlice(arr,ini=0,fin=0) {
  if (ini<0) ini = arr.length+ini;
  if (fin==0) fin = arr.length;
  else if (fin<0) fin = arr.length+fin;
  else fin = ini+fin
  var sliced = [];
  for (var i = 0; i < arr.length; i++) {
    sliced.push([]); 
    for (var j = 0; j < arr[i].length; j++) {
      if (j>=ini && j<fin)
        sliced[i].push(arr[i][j]);
    }
  }
  return sliced;
}
function colJoin(a,b) {
  var joined = [];
  for (var i = 0; i < a.length; i++) {
    joined.push([]); 
    for (var j = 0; j < a[i].length; j++) {
      joined[i].push(a[i][j]);
    }
    for (var j = 0; j < b[i].length; j++) {
      joined[i].push(b[i][j]);
    }
  }
  return joined;
}
function sortTable(r,c) {
  var table = document.getElementById("myTable");
  var rows = table.getElementsByTagName("TR");
  var matriz = [];
  var m = null;
  // Get matrix from table
  for (i = 0; i < rows.length; i++) {
    if (i==0) cols = rows[i].getElementsByTagName("TH");
    else cols = rows[i].getElementsByTagName("TD");
    m = [];
    for (j = 0; j < cols.length; j++) {
      m.push(cols[j].innerHTML);
    }
    matriz.push(m);
  }
  if (r==0) { // sort rows by col
    if (c==cc) dirc = Math.abs(1-dirc);
    else dirc = 0;
    m = matriz[0];
    matriz = matriz.slice(1);
    matriz.sort(function(a, b){
      if (dirc<=0) {
        if (a[c].toLowerCase() > b[c].toLowerCase()) return 1;
        else if (a[c].toLowerCase() < b[c].toLowerCase()) return -1;
      } else {
        if (a[c].toLowerCase() > b[c].toLowerCase()) return -1;
        else if (a[c].toLowerCase() < b[c].toLowerCase()) return 1;
      }
      return 0;
    });
    matriz.unshift(m);
    cc = c;
  }
  if (c==0) { // sort cols by row
    if (r==rr) dirr = Math.abs(1-dirr);
    else dirr = 0;
    m = colSlice(matriz,0,1);
    matriz = colSlice(matriz,1);
    // Transpose matriz
    var newArray = matriz[0].map(function(col, i){
      return matriz.map(function(row){
        return row[i];
      });
    });
    // Sort
    newArray.sort(function(a, b){
      if (dirr<=0) {
        if (a[r].toLowerCase() > b[r].toLowerCase()) return 1;
        else if (a[r].toLowerCase() < b[r].toLowerCase()) return -1;
      } else {
        if (a[r].toLowerCase() > b[r].toLowerCase()) return -1;
        else if (a[r].toLowerCase() < b[r].toLowerCase()) return 1;
      }
      return 0;
    });
    // Transpose back
    matriz = newArray[0].map(function(col, i){
      return newArray.map(function(row){
        return row[i];
      });
    });
    matriz = colJoin(m,matriz);
    rr = r;
  }
  // Update values
  for (i = 0; i < rows.length; i++) {
    if (i==0) cols = rows[i].getElementsByTagName("TH");
    else cols = rows[i].getElementsByTagName("TD");
    for (j = 0; j < cols.length; j++) {
      cols[j].innerHTML = matriz[i][j];
    }
  }
}
/* Just for beauty */
  #myTable {
    border: none;
  }
  #myTable th {
    cursor: n-resize;
    border: none;
    background-color: #e0e0e0;
  }
  #myTable th:first-child {
    cursor: move;
  }
  th, td {
    width: 5%;
  }
  #myTable tr td {
    border: none;
    border-bottom: 1px solid #aaa;
    text-align: center;
  }
  #myTable tr td:first-child {
    cursor: e-resize;
    background-color: #e0e0e0;
    border-bottom: 1px solid #aaa;
    text-align: center;
  }
  #myTable tr:last-child td {
    border: none;
  }
<table id="myTable">
  <tbody>
    <tr>
      <th onclick="sortTable(0,0)">Both</th>
      <th onclick="sortTable(0,1)">Col A</th>
      <th onclick="sortTable(0,2)">Col B</th>
      <th onclick="sortTable(0,3)">Col C</th>
    </tr>
    <tr>
      <td onclick="sortTable(1,0)">Row A</td>
      <td>2</td>
      <td>3</td>
      <td>1</td>
    </tr>
    <tr>
      <td onclick="sortTable(2,0)">Row B</td>
      <td>1</td>
      <td>3</td>
      <td>2</td>
    </tr>
    <tr>
      <td onclick="sortTable(3,0)">Row C</td>
      <td>3</td>
      <td>2</td>
      <td>1</td>
    </tr> 
  </tbody>
</table>

Cheers!

Upvotes: 1

Sanxofon
Sanxofon

Reputation: 981

I often asked my self that question but never seem to really need it so never went any further. Your answer is great, but you'll have issues when comparing numbers as strings. It does not shows in your code because you are using 1, 2, 3 values. Check my snippet with mixed data types, of course the code can be reduced a lot.

var dirc = 0, dirr = 0, cc = 0, rr = 0;
function colSlice(arr,ini=0,fin=0) {
  if (ini<0) ini = arr.length+ini;
  if (fin==0) fin = (arr.length+1)-ini;
  else fin = ini+fin
  var sliced = [];
  for (var i = 0; i < arr.length; i++) {
    sliced.push([]); 
    for (var j = 0; j < arr[i].length; j++) {
      if (j>=ini && j<fin)
        if (parseFloat(arr[i][j])>0 || arr[i][j]=="0")
          sliced[i].push(parseFloat(arr[i][j]));
        else 
          sliced[i].push(arr[i][j]);
    }
  }
  return sliced;
}
function colJoin(a,b) {
  var joined = [];
  for (var i = 0; i < a.length; i++) {
    joined.push([]); 
    for (var j = 0; j < a[i].length; j++) {
      joined[i].push(a[i][j]);
    }
    for (var j = 0; j < b[i].length; j++) {
      joined[i].push(b[i][j]);
    }
  }
  return joined;
}

function sortMatrix(a,b,x,d) {
  if (d<=0) var ret = [1,-1];
  else var ret = [-1,1];
  if (isNaN(a[x]) && isNaN(b[x])) {
    var ax = a[x].toLowerCase();
    var bx = b[x].toLowerCase();
  } else if (isNaN(a[x])) {
    return ret[0];
  } else if (isNaN(b[x])) {
    return ret[1];
  } else {
    var ax = parseFloat(a[x]);
    var bx = parseFloat(b[x]);
  }
  if (ax > bx) return ret[0];
  else if (ax < bx) return ret[1];
  else return 0;
}
function sortTable(r,c) {
  var table = document.getElementById("myTable");
  var rows = table.getElementsByTagName("TR");
  var matriz = [];
  var m = null;
  // Get matrix from table
  for (i = 0; i < rows.length; i++) {
    if (i==0) cols = rows[i].getElementsByTagName("TH");
    else cols = rows[i].getElementsByTagName("TD");
    m = [];
    for (j = 0; j < cols.length; j++) {
      m.push(cols[j].innerHTML);
    }
    matriz.push(m);
  }
  if (r==0) { // sort rows by col
    if (c==cc) dirc = Math.abs(1-dirc);
    else dirc = 0;
    m = matriz[0];
    matriz = matriz.slice(1);
    matriz.sort(function(a, b){
      return sortMatrix(a,b,c,dirc);
    });
    matriz.unshift(m);
    cc = c;
  }
  if (c==0) { // sort cols by row
    if (r==rr) dirr = Math.abs(1-dirr);
    else dirr = 0;
    m = colSlice(matriz,0,1);
    matriz = colSlice(matriz,1);
    // Transpose matriz
    var newArray = matriz[0].map(function(co, i){
      return matriz.map(function(ro){
        return ro[i];
      });
    });
    // Sort
    newArray.sort(function(a, b){
      return sortMatrix(a,b,r,dirr);
    });
    // Transpose back
    matriz = newArray[0].map(function(co, i){
      return newArray.map(function(ro){
        return ro[i];
      });
    });
    matriz = colJoin(m,matriz);
    rr = r;
  }
  // Update values
  for (i = 0; i < rows.length; i++) {
    if (i==0) cols = rows[i].getElementsByTagName("TH");
    else cols = rows[i].getElementsByTagName("TD");
    for (j = 0; j < cols.length; j++) {
      cols[j].innerHTML = matriz[i][j];
    }
  }
}
function clicRow(e) {
  sortTable(0,parseInt(e.target.attributes.col.value));
}
var tds=document.querySelectorAll("#myTable tr td:first-child,#myTable tr th:first-child");
var len = tds.length;
for(var i=0; i< len; i++){
  tds[i].addEventListener('click', clicRow);
}
function clicCol(e) {
  sortTable(parseInt(e.target.attributes.row.value),0);
}
var tds=document.querySelectorAll("#myTable tr:first-child th");
var len = tds.length;
for(var i=1; i< len; i++){
  tds[i].addEventListener('click', clicCol);
}
  /* Just for beauty */
  #myTable {
    border: none;
  }
  #myTable th {
    cursor: n-resize;
    border: none;
    background-color: #e0e0e0;
  }
  #myTable th:first-child {
    cursor: move;
  }
  th, td {
    width: 5%;
  }
  #myTable tr td {
    border: none;
    border-bottom: 1px solid #aaa;
    text-align: center;
  }
  #myTable tr td:first-child {
    cursor: e-resize;
    background-color: #e0e0e0;
    border-bottom: 1px solid #aaa;
    text-align: center;
  }
  #myTable tr:last-child td {
    border: none;
  }
<table id="myTable">
  <tbody>
    <tr>
      <th row="0" col="0">Both</th>
      <th row="1">Col A</th>
      <th row="2">Col B</th>
      <th row="3">Col C</th>
      <th row="4">Col 4</th>
    </tr>
    <tr>
      <td col="1">Row A</td>
      <td>221</td>
      <td>2</td>
      <td>22</td>
      <td>22.2</td>
    </tr>
    <tr>
      <td col="2">Row B</td>
      <td>123</td>
      <td>1234</td>
      <td>ABA</td>
      <td>aba</td>
    </tr>
    <tr>
      <td col="3">Row C</td>
      <td>1</td>
      <td>12.04</td>
      <td>12.4</td>
      <td>ab</td>
    </tr> 
    <tr>
      <td col="4">Row 4</td>
      <td>123.2</td>
      <td>123.1</td>
      <td>acar</td>
      <td>acar</td>
    </tr> 
  </tbody>
</table>

Update: Simplified and removed onClick calls.

Upvotes: 1

Related Questions