Noam Rathaus
Noam Rathaus

Reputation: 5598

Sorting HTML table with subitems with JS

I have the following table:

<table border="0" cellspacing="0" cellpadding="0" id="table1">
    <tbody>
        <tr>
            <th onclick="sortTable(0, this); return false;" class="sort-up" order="-1">ColumnA</th>
            <th style="width: 12em;" onclick="sortTable(1, this); return false;" class="sort-none">ColumnB</th>
            <th style="width: 9em;" onclick="sortTable(2, this); return false;" class="sort-none">ColumnC</th>
            <th style="width: 10em;" onclick="sortTable(3, this); return false;" class="sort-none">ColumnD</th>
            <th style="width: 6em;">ColumnE</th>
        </tr>
        <tr id="tr217E9B6C" type="root" level="217E9B6C" depth="0">
            <td class="evenListRow" id="nocenter">
                <div class="tier1">Root A</div>
            </td>
            <td class="evenListRow">1</td>
            <td class="evenListRow">2</td>
            <td class="evenListRow">3</td>
            <td class="evenListRow">4</a>
            </td>
        </tr>
        <tr id="tr217E9B6C-6E781501" type="sub" level="217E9B6C-6E781501" depth="1">
            <td class="oddListRow" id="nocenter">
                <div class="tier2">Sub A</div>
            </td>
            <td class="oddListRow">5</td>
            <td class="oddListRow">6</td>
            <td class="oddListRow">7</td>
            <td class="oddListRow">8</td>
        </tr>
        <tr id="tr217E9B6C-852AB6E5" type="sub" level="217E9B6C-852AB6E5" depth="1">
            <td class="evenListRow" id="nocenter">
                <div class="tier2">Sub B</div>
            </td>
            <td class="evenListRow">9</td>
            <td class="evenListRow">10</td>
            <td class="evenListRow">11</td>
            <td class="evenListRow">12</td>
        </tr>
        <tr id="tr2BE7EAFE" type="root" level="2BE7EAFE" depth="0">
            <td class="evenListRow" id="nocenter">
                <div class="tier1">Root B</div>
            </td>
            <td class="evenListRow">13</td>
            <td class="evenListRow">14</td>
            <td class="evenListRow">15</td>
            <td class="evenListRow">16</td>
        </tr>
        <tr id="tr2BE7EAFE-49A04568" type="sub" level="2BE7EAFE-49A04568" depth="1">
            <td class="oddListRow" id="nocenter">
                <div class="tier2">Sub C</div>
            </td>
            <td class="oddListRow">17</td>
            <td class="oddListRow">18</td>
            <td class="oddListRow">19</td>
            <td class="oddListRow">20</td>
        </tr>
        <tr id="tr2BE7EAFE-DAE218A5" type="sub" level="2BE7EAFE-DAE218A5" depth="1">
            <td class="evenListRow" id="nocenter">
                <div class="tier2">Sub D</div>
            </td>
            <td class="evenListRow">21</td>
            <td class="evenListRow">22</td>
            <td class="evenListRow">23</td>
            <td class="evenListRow">24</td>
        </tr>
        <tr id="tr4FFACE4A" type="root" level="4FFACE4A" depth="0">
            <td class="oddListRow" id="nocenter">
                <div class="tier1">Root C</div>
            </td>
            <td class="oddListRow">25</td>
            <td class="oddListRow">26</td>
            <td class="oddListRow">27</td>
            <td class="oddListRow">28</td>
        </tr>
        <tr id="tr4FFACE4A-B9A443CA" type="sub" level="4FFACE4A-B9A443CA" depth="1">
            <td class="evenListRow" id="nocenter">
                <div class="tier2">Sub E</div>
            </td>
            <td class="evenListRow">29</td>
            <td class="evenListRow">30</td>
            <td class="evenListRow">31</td>
            <td class="evenListRow">32</td>
        </tr>
    </tbody>
</table>

And I want to sort it, first by the "root" then by the "sub" items, which would mean that Root A will always have its Sub A, Sub B under it (sorted as well, but under it)

I used the following code which works on only on the "sub items", I cannot get it to work by doing a "mix", i.e. top and sub (separately sorted)

function sortTable(column, thisrow) {
 var order = thisrow.getAttribute('order');
 if (!order) {
  order = 1;
 }

 var tbl = document.getElementById("table1").tBodies[0];
 if (!tbl) {
  return;
 }

 if (previousSortColumn && previousSortColumn.innerHTML != thisrow.innerHTML) {
  previousSortColumn.setAttribute('class', 'sort-none');
 }
 previousSortColumn = thisrow;

 var store = [];
 /* Build a store object that has every element in the table, we will use this to sort */
 for(var rowpos=1, len=tbl.rows.length; rowpos<len; rowpos++) { // skip row #1 as it is the header
  var row = tbl.rows[rowpos];

  var i_textContent = row.cells[column].textContent;

  while(i_textContent.indexOf(' ') != -1) { // remove spaces
   i_textContent = i_textContent.replace(' ', '');
  }

  var sortnr = i_textContent;
  var type = row.getAttribute('type');
  var level = row.getAttribute('level');
  var depth = row.getAttribute('depth');

  store.push({sortnr: sortnr, row:row, storelength:store.length, type:type, level:level, depth:depth});
 }

 /* We sort first roots then the elements under it */
 store.sort(function(x,y) {
  var xtype = x['type'];
  var ytype = y['type'];

  var result;

  if (xtype == 'root' && ytype == 'root')
  {
   result = x['sortnr'].localeCompare(y['sortnr']);
  } else {
   return 0;
  }

  if (order == 1) {
   return result;
  } else {
   return -1 * result;
  }
 });

 /* We sort the elements under it */
 store.sort(function(x,y) {
  var xtype = x['type'];
  var ytype = y['type'];

  var xlevel = x['level'];
  var ylevel = y['level'];

  if (xlevel.lastIndexOf('-') > 0) {
   xlevel = xlevel.substring(0, xlevel.lastIndexOf('-'));
  }

  if (ylevel.lastIndexOf('-') > 0) {
   ylevel = ylevel.substring(0, ylevel.lastIndexOf('-'));
  }

  if (xlevel != ylevel || xtype == 'root' || ytype == 'root')
  {
   return x['storelength'] - y['storelength']; // return order inside array
  }

  var result = x['sortnr'].localeCompare(y['sortnr']);

  if (order == 1) {
   return result;
  } else {
   return -1 * result;
  }
 });

 for(var i=0; i < store.length; i++) {
  tbl.appendChild(store[i]['row']);
 }
 store = null;
}

Update 1: Clicking once on 'ColumnB' would not affect the table (a bit of a bad example on my part), as the information is already sorted in the correct order, however another click should sort everything in reverse order

So both the Roots would be in reverse other, Root C, Root B, Root A, as well their sub items, Sub D before Sub C, ...

<table border="0" cellspacing="0" cellpadding="0" id="table1">
    <tbody>
        <tr>
            <th onclick="sortTable(0, this); return false;" class="sort-up" order="-1">ColumnA</th>
            <th style="width: 12em;" onclick="sortTable(1, this); return false;" class="sort-none">ColumnB</th>
            <th style="width: 9em;" onclick="sortTable(2, this); return false;" class="sort-none">ColumnC</th>
            <th style="width: 10em;" onclick="sortTable(3, this); return false;" class="sort-none">ColumnD</th>
            <th style="width: 6em;">ColumnE</th>
        </tr>
        <tr id="tr4FFACE4A" type="root" level="4FFACE4A" depth="0">
            <td class="oddListRow" id="nocenter">
                <div class="tier1">Root C</div>
            </td>
            <td class="oddListRow">25</td>
            <td class="oddListRow">26</td>
            <td class="oddListRow">27</td>
            <td class="oddListRow">28</td>
        </tr>
        <tr id="tr4FFACE4A-B9A443CA" type="sub" level="4FFACE4A-B9A443CA" depth="1">
            <td class="evenListRow" id="nocenter">
                <div class="tier2">Sub E</div>
            </td>
            <td class="evenListRow">29</td>
            <td class="evenListRow">30</td>
            <td class="evenListRow">31</td>
            <td class="evenListRow">32</td>
        </tr>
        <tr id="tr2BE7EAFE" type="root" level="2BE7EAFE" depth="0">
            <td class="evenListRow" id="nocenter">
                <div class="tier1">Root B</div>
            </td>
            <td class="evenListRow">13</td>
            <td class="evenListRow">14</td>
            <td class="evenListRow">15</td>
            <td class="evenListRow">16</td>
        </tr>
        <tr id="tr2BE7EAFE-DAE218A5" type="sub" level="2BE7EAFE-DAE218A5" depth="1">
            <td class="evenListRow" id="nocenter">
                <div class="tier2">Sub D</div>
            </td>
            <td class="evenListRow">21</td>
            <td class="evenListRow">22</td>
            <td class="evenListRow">23</td>
            <td class="evenListRow">24</td>
        </tr>
        <tr id="tr2BE7EAFE-49A04568" type="sub" level="2BE7EAFE-49A04568" depth="1">
            <td class="oddListRow" id="nocenter">
                <div class="tier2">Sub C</div>
            </td>
            <td class="oddListRow">17</td>
            <td class="oddListRow">18</td>
            <td class="oddListRow">19</td>
            <td class="oddListRow">20</td>
        </tr>
        <tr id="tr217E9B6C" type="root" level="217E9B6C" depth="0">
            <td class="evenListRow" id="nocenter">
                <div class="tier1">Root A</div>
            </td>
            <td class="evenListRow">1</td>
            <td class="evenListRow">2</td>
            <td class="evenListRow">3</td>
            <td class="evenListRow">4</a>
            </td>
        </tr>
        <tr id="tr217E9B6C-852AB6E5" type="sub" level="217E9B6C-852AB6E5" depth="1">
            <td class="evenListRow" id="nocenter">
                <div class="tier2">Sub B</div>
            </td>
            <td class="evenListRow">9</td>
            <td class="evenListRow">10</td>
            <td class="evenListRow">11</td>
            <td class="evenListRow">12</td>
        </tr>
        <tr id="tr217E9B6C-6E781501" type="sub" level="217E9B6C-6E781501" depth="1">
            <td class="oddListRow" id="nocenter">
                <div class="tier2">Sub A</div>
            </td>
            <td class="oddListRow">5</td>
            <td class="oddListRow">6</td>
            <td class="oddListRow">7</td>
            <td class="oddListRow">8</td>
        </tr>
    </tbody>
</table>

Upvotes: 3

Views: 607

Answers (1)

Laurentiu L.
Laurentiu L.

Reputation: 6686

I solved your problem. I did reorganize the code to make it a lot more readable. Most of the logic is what you provided, i just added small bits. And btw, you have duplicate id references on id="nocenter" in your html.

Here is a working jsfiddle of my solution. The HTML is exactly the one you provided , with errors and all and no listener on column E. This js fiddle version has some more subs on root A. You can play with it as you will (add extra data). Summary of the code comes after it in the answer.


Update - taken your new input data in the comments , i updated the jsfiddle it would seem i kept the root in place, and it jumped out of subset. It was a matter of changing 1 line in sorting the subs. The new fiddle.

var ASC = 1;
var DESC = -1;
var SORTNR_INDEX = 0;
var LOWER = 1;
var UPPER = 2;
var previousSortColumn ;
var order;

/* The original build store you provided */
var buildStore = function(column,tbl){
    var store = [];
    for (var rowpos = 1, len = tbl.rows.length; rowpos < len; rowpos++) { // skip row #1 as it is the header
        var row = tbl.rows[rowpos];
        var i_textContent = row.cells[column].textContent;
        while (i_textContent.indexOf(' ') != -1) { // remove spaces
            i_textContent = i_textContent.replace(' ', '');
        }
        var sortnr = i_textContent;
        var type = row.getAttribute('type');
        var level = row.getAttribute('level');
        var depth = row.getAttribute('depth');
        store.push({sortnr: sortnr, row: row, storelength: store.length, type: type, level: level, depth: depth});
    }
    return store;
}
// the order convention you offered
var triggerOrder = function(){
    if (order==ASC){
        order = DESC;
    } else if (order==DESC || !order){
        order = ASC;
    }
}

// the code you provided
var getLevel = function(obj){
    if (obj && obj.lastIndexOf('-') > 0) {
        return obj.substring(0, obj.lastIndexOf('-'));
    }
    return obj;
}

function sortRoot(a,b){
    var aSort = a[SORTNR_INDEX], bSort = b[SORTNR_INDEX];
    return compareWithOrder(aSort,bSort);
};
var sortSubs = function(x,y){
    var xtype = x['type'];
    var ytype = y['type'];
    if (xtype == 'root'){
        return -1;
    } else if (xtype == ytype) {
        var xSort = x['sortnr'];
        var ySort = y['sortnr'];
        return compareWithOrder(xSort,ySort);
    }
}
var compareWithOrder = function(x,y){
    if (isNaN(parseInt(x))) {
        return order * x.localeCompare(y);
    } else {
        x = parseInt(x);
        y = parseInt(y);
        if (x < y) {
            return -1 * order;
        } else if (x > y) {
            return 1 * order;
        } else {
            return 0;
        }
    }
};
//assumes they are aligned by depth (i.e. will always have a root then subs). if not, an additional sort can be made beforehand
function getGroupsByLevel(store){
    var group = [];
    var groupIndex=0;
    var lower =0, upper, sortNo;
    if (store.length > 0) {
        var x,y;
        for (var i = 0; i < store.length; i++) {
            x = store[i];
            if (store[i+1]){
                y = store[i+1]
            } else{
                y = {};
            }
            var xtype = x['type'];
            var ytype = y['type'];
            if (xtype=='root'){
                sortNo = x['sortnr'];
            }
            var xlevel = getLevel(x['level']);
            var ylevel = getLevel(y['level']);
            if (xlevel != ylevel){
                group[groupIndex] = [sortNo,lower,i];
                lower=i+1;
                groupIndex++;
            }
        }
    }
    return group;
};
function sortTable(column, thisrow) {
    order = thisrow.getAttribute('order');
    triggerOrder();
    thisrow.setAttribute('order',order);

    var tbl = document.getElementById("table1").tBodies[0];
    if (!tbl) return;

    /* Build a store object that has every element in the table, we will use this to sort */
    var store = buildStore(column,tbl);

    var groups = getGroupsByLevel(store);
    groups.sort(sortRoot);
    var newStore=[];
    for (var i=0;i<groups.length;i++){
        var group = groups[i];
        var rootAndSubs = store.slice(group[LOWER],group[UPPER]+1);
        rootAndSubs.sort(sortSubs);
        newStore=newStore.concat(rootAndSubs);
    }
    //update table
    for (var i = 0; i < newStore.length; i++) {
        tbl.appendChild(newStore[i]['row']);
    }
    store = null;
    order = null;
}

Basically it goes like this:

  • set +trigger the order
  • build the store just like you intended
  • get groups of roots and their subs
  • sort the groups and then sort the each of the subs for the group
  • update the view

This is the first approach i thought of.

Upvotes: 3

Related Questions