7

Following this post jQuery table sort (github link: https://github.com/padolsey/jQuery-Plugins/blob/master/sortElements/jquery.sortElements.js), I am successfully sort columns, however it does not work in the case of rowspan: For example, case like this

 Grape      3,096,671M
            1,642,721M
 Apple      2,602,750M
            3,122,020M

When I click on the second column, it try to sort

 Apple      2,602,750M
            1,642,721M
 Grape      3,096,671M
            3,122,020M

(Expected result should be that it should only sort within each rowspan

 Grape      1,642,721M
            3,096,671M
 Apple      2,602,750M
            3,122,020M

or

 Grape      3,096,671M
            1,642,721M
 Apple      3,122,020M
            2,602,750M

)

so either which as you can see is not correct, please any jQuery guru help me fix this problem. Here is my code

var inverse = false;
function sortColumn(index){
    index = index + 1;
    var table = jQuery('#resultsTable');
    table.find('td').filter(function(){
        return jQuery(this).index() == index;
    }).sortElements(function(a, b){
        a = convertToNum($(a).text());
        b = convertToNum($(b).text());

        return (
            isNaN(a) || isNaN(b) ?
            a > b : +a > +b
            ) ?
        inverse ? -1 : 1 :
        inverse ? 1 : -1;
    },function(){
        return this.parentNode;
    });
    inverse = !inverse;
}
function convertToNum(str){
    if(isNaN(str)){
        var holder = "";
        for(i=0; i<str.length; i++){                                
            if(!isNaN(str.charAt(i))){
                holder += str.charAt(i);
            }
        }
        return holder;
    }else{
        return str;
    }
}

Question:

1.How do I sort this with rowspan. THE NUMBER OF ROWSPAN IS NOT ALWAYS THE SAME. The above example both Grape and Apple have rowspan of 2, but this is not always the case.

2.Can any explain this syntax:

 return (
            isNaN(a) || isNaN(b) ?
            a > b : +a > +b
            ) ?
        inverse ? -1 : 1 :
        inverse ? 1 : -1;

So I can see that if either a or b is not a number, then do string comparison otherwise do number comparison, but I dont understand the

inverse ? -1 : 1 :
inverse ? 1 : -1;

Test cases

<table id="resultsTable">
        <thead>
            <tr>
                <th>Fruit</th>
                <th onclick="sortColumn(1)">Quantity</th>
                <th onclick="sortColumn(2)">Rate</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td rowspan="4">Grape</td>
                <td>15</td>
                <td>5</td>
            </tr>
            <tr>
                <td>4</td>
                <td>2</td>
            </tr>
            <tr>
                <td>88</td>
                <td>1</td>
            </tr>
            <tr>                    
                <td>11</td>
                <td>3</td>
            </tr>
            <tr>
                <td rowspan="3">Melon</td>
                <td>21</td>
                <td>2</td>
            </tr>
            <tr>
                <td>2</td>
                <td>0</td>
            </tr>
            <tr>
                <td>35</td>
                <td>1</td>
            </tr>
            <tr>
                <td rowspan="6">Melon</td>
                <td>24</td>
                <td>5</td>
            </tr>
            <tr>
                <td>66</td>
                <td>2</td>
            </tr>
            <tr>
                <td>100</td>
                <td>4</td>
            </tr>
            <tr>
                <td>21</td>
                <td>1</td>
            </tr>
            <tr>
                <td>65</td>
                <td>3</td>
            </tr>
            <tr>
                <td>2</td>
                <td>0</td>
            </tr>
        </tbody>
 <table>
Community
  • 1
  • 1
Thang Pham
  • 38,125
  • 75
  • 201
  • 285

2 Answers2

3

Conditions for the code to work:

  • Columns containing tds with rowspan must all be on the left of the table
  • All the tds in these columns must have a rowspan, even if it's 1
  • The groups of rows to sort are made with the rightmost of these columns (but it can easily be changed)

jsFiddle: http://jsfiddle.net/5GrAC/77/

var inverse = false;

function sortColumn(index) {
    var trs = $('#resultsTable > tbody > tr'),
        nbRowspans = trs.first().children('[rowspan]').length,
        offset = trs.first().children('[rowspan]').last().offset().left;

    var tds = trs.children('[rowspan]').each(function() {
        $(this).data('row', $(this).parent().index());
        $(this).data('column', $(this).index());
        $(this).data('offset', $(this).offset().left)
    }).each(function() {
        if($(this).data('offset') != offset)
            return;

        var rowMin = $(this).data('row'),
            rowMax = rowMin + parseInt($(this).attr('rowspan'));

        trs.slice(rowMin, rowMax).children().filter(function() {
            return $(this).index() == index + $(this).parent().children('[rowspan]').length - nbRowspans;
        }).sortElements(function(a, b) {
            a = convertToNum($(a).text());
            b = convertToNum($(b).text());

            return (
                isNaN(a) || isNaN(b) ?
                a > b : +a > +b
                ) ?
            inverse ? -1 : 1 :
            inverse ? 1 : -1;
        }, function() {
            return this.parentNode;
        });
    });

    var trs = $('#resultsTable > tbody > tr');
    tds.each(function() {
        if($(this).parent().index() != $(this).data('row'))
            $(this).insertBefore(trs.eq($(this).data('row')).children().eq($(this).data('column')));
    });

    inverse = !inverse;
}

Quick explanations:

  • Finding all tds with rowspan
  • Positions of these tds are saved, including left offset
  • These tds are filtered by their original offset to work only with the rightmost ones
  • trs related to each kept td are sorted using the wanted column
  • All tds with rowspan are finally moved back to their original position if necessary

About question 2, I will only complete bartlaarhoven's answer by saying, the code can also be written like the following:

return (
        (isNaN(a) || isNaN(b) ? a > b : +a > +b) ? 1 : -1
    ) * (inverse ? -1 : 1);

You can easily read that inverse is used to inverse the result.

Newbo.O
  • 1,948
  • 1
  • 13
  • 14
  • So srry, because the way I pass in index, I need `index = index + 1`, however, in your case, please take it out. Also, I used your code but the table is broken, I put the table I use for testing in my question, can you take a look at it please. Thank you so much. I am investigate it as well. Thank you – Thang Pham Oct 18 '12 at 17:05
  • My mistake, I was sometimes moving a `td` into the `thead`... I've updated my answer to fix that. I've also created a jsfiddle with your data sample (I didn't put any CSS so it's a bit ugly but it works) – Newbo.O Oct 18 '12 at 18:08
  • Thank you, you are so awesome, one more things. The mmiddle block (the middle `Melon` block), its sorting is reverse with everyone else. Can you take a look at it please. Thank you so so much :D – Thang Pham Oct 18 '12 at 18:58
  • If I move the inverse = !inverse to the end, then it works correctly :D – Thang Pham Oct 18 '12 at 19:05
  • would you take a moment and explain this code for me please? `if($(this).parent().index() != trIndexMin) $(this).insertBefore(table.find('tbody tr').eq(trIndexMin).children().eq(rowspanTdIndex));` The way I see it,`$(this)` is the td with rowspan, so the parent is tr, so you get the index of the tr that contain the td with rowspan, and you insert this tr to some thing, I cannot see it yet – Thang Pham Oct 18 '12 at 19:15
  • nvm :D, I see what you doing, you locate that td with rowspan, and insert it back to the first tr of the block. That is very clever – Thang Pham Oct 18 '12 at 19:30
  • I was actually writting an answer but you found out by yourself :) – Newbo.O Oct 18 '12 at 19:32
  • Thank you very much for all your help. I just have one more question, so my table is very large, and when I sort, it actually stop every from working, and sometimes even crash my browser, is there anything that I can do here? Once again, thank you very much – Thang Pham Oct 18 '12 at 20:13
  • 2
    I optimized a bit the code (there were way too many searches and filterings), it should work better with large tables. – Newbo.O Oct 18 '12 at 21:20
  • So awesome, it is much better, I will investigate on the case where we have more rowspan on the left, but thank you very very much for your work. – Thang Pham Oct 19 '12 at 14:30
  • 1
    Answer updated so it works with more rowspans on the left. There are still conditions to be able to use the code but I think it would be **really** hard to have a more generic solution ^^ – Newbo.O Oct 19 '12 at 19:58
  • Thank you very much. For the case of two columns, with the left one have rowspan = 1, I just handle that case separately, since this approach will have rowMin and rowMax are 0 and 1 respectively, so nothing get sorted. Thank you very much for your awesome work. Bounty granted. If I have more question about jQuery sort, i hope I can come to you again. Once again thank you very much – Thang Pham Oct 19 '12 at 23:36
  • Hi @Newbo.O im trying to adapt your function to one personal project but i get this error: **Uncaught ReferenceError: index is not defined**. the error comes from this line: `return $(this).index() == index + $(this).parent().children('[rowspan]').length - $rowspans;` that index with no parenteses function what is? it has no value at all, not defined. can you give more light to this please? – Fraccier Apr 18 '16 at 07:31
  • `index` is the `sortColumn` function parameter. You have to pass it to tell which column to use for sorting, otherwise it'll indeed be undefined. – Newbo.O Apr 19 '16 at 12:49
2

Considering question 1, try this code:

var inverse = false;
var curRowSpan = 0;
var curIndex = 0;
var doRowSpan = false;
function sortColumn(index){
    index = index + 1;
    var table = jQuery('#resultsTable');
    table.find('td').filter(function() {
        var result = false;
        // if it is a column before the sorting column, watch the rowSpan
        if (curRowSpan == 0 && jQuery(this).index() < index && jQuery(this).attr("rowspan") > 1) {
            curRowSpan = jQuery(this).attr("rowspan");
            doRowSpan = true;
            // we are not in the sorting column so we can safely continue
            continue;
        }

        if(!doRowSpan) curIndex = index - (curRowSpan?1:0);
        else curIndex = index;

        if(jQuery(this).index() == curIndex) {
            // we are at the sorting column
            if(curRowSpan > 0) {
                curRowSpan--;
            }
            // set this to false for the following row
            doRowSpan = false;
            result = true;
        }

        return result;
    }).sortElements(function(a, b){
        a = convertToNum($(a).text());
        b = convertToNum($(b).text());

        return (
            isNaN(a) || isNaN(b) ?
            a > b : +a > +b
        ) ?
            inverse ? -1 : 1 :
            inverse ? 1 : -1;
        },function(){
            return this.parentNode;
        });
        inverse = !inverse;
    }
    function convertToNum(str){
        if(isNaN(str)){
            var holder = "";
            for(i=0; i<str.length; i++){                                
                if(!isNaN(str.charAt(i))){
                    holder += str.charAt(i);
                }
            }
            return holder;
        }else{
            return str;
        }
    }

Considering question 2; let's deduct where it comes from. First of all we would like to check whether a > b, and, as you already saw correctly, we make a difference between string comparison and number comparison.

We would then simply give:

return (
        isNaN(a) || isNaN(b) ?
        a > b : +a > +b
        ) ? 1 : -1;

This checks whether a > b (either string-wise or number-wise) and then returns 1 on true and -1 on false.

Now we insert the inverse parameter, which should inverse the result. This means that if inverse == true, then 1 becomes -1 and -1 becomes 1. In code, this bold piece of text would replace each occurrence of 1 with inverse ? -1 : 1 and each occurrence of -1 with inverse ? 1 : -1. Which is exactly what is done in the resulting code.

UPDATE: Added doRowSpan in the code because it should not adapt the index if we are in the <tr> that contains the rowspan-td.

bartlaarhoven
  • 825
  • 2
  • 8
  • 21
  • Thank you very much. When I plug in your code I got this javascript error `Uncaught SyntaxError: Illegal continue statement`. Can u check, I use `http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js` – Thang Pham Oct 18 '12 at 16:53
  • because the way I pass in index, I need index = index + 1, however, in your case, please take it out. Also, I used your code (without the continue) but the table is broken, I put the table that I use for testing in my question, can you take a look at it please. Thank you so much. I am investigate it as well. Thank you – Thang Pham Oct 18 '12 at 17:06