0

The problem is that it will not allow me to insert a row into my table using jQuery. I tried using .eq() with index to find the correct position to insert and .after() to insert the row. However, it will not add the row.

This is the HTML:

<table class="ResultsTable" hidden="hidden">
    <thead class="header">
        <tr>
            <td class="rankColumn"></td>
            <td class="nameColumn"></td>
            <td class="ageColumn"></td>                        
        </tr>
    </thead>
    <tbody id="resultsTableBody"></tbody>
</table>

This is my jQuery:

function insert(data){
    $('#resultsTableBody > tr')
    .eq(data['Status'] - 1).after('<tr>' +
    '<td class=\'rankColumn ' + data['Rank'] + ' \' ></td>' +
    '<td class=\'nameColumn\'>' + data['Name'] + '</td>' +
    '<td class=\'ageColumn\'>' + data['Age'] + '</td>' +
    '</tr>');
}

Why is it that it wont insert anything into the table body? Rank is like an index which represents how high up the list a row should be. With rank being from 1 to 3. So this code should insert a row into the correct position in the table. The table is empty to start with.

Expected result is:

1 | John      | 24
1 | Bob       | 19
1 | Misha     | 27
2 | Laura     | 22
3 | Hannah    | 31
3 | Paul      | 43  

They should be placed in order like above based on their rank. However, the table just shows the header and the body is blank. Where am I going wrong?

The data will all not be available when inserting they will be retrieved one at a time and it will find its place in the table based on the others.

Yoav Kadosh
  • 4,807
  • 4
  • 39
  • 56
KB_Shayan
  • 632
  • 8
  • 24

2 Answers2

2

You can use a data attribute to specify the rank on the rows (i.e. data-rank), then sort the table on every insert based on that:

function insert({Status, Rank, Name, Age}) {
    const html = `<tr data-rank="${Rank}">
        <td class="rankColumn">${Rank}</td>
        <td class="nameColumn">${Name}</td>
        <td class="ageColumn">${Age}</td>
    </tr>`;

    $('#resultsTableBody').append(html);

    // Sort the rows based on rank
    $('#resultsTableBody > tr')
    .sort((a, b) => $(a).attr('data-rank') - $(b).attr('data-rank'))
    .appendTo('#resultsTableBody');
}

Also, using Template Literals and Object Destructuring will make your code more readable.

Here's a fiddle.

A More Performant Algorithm

As noted in the comments the above function rerenders the entire table on every insert, which is a resource-heavy operation.

To overcome that, we can maintain an array with all the current ranks in the table. Every time a new row is to be inserted, we check against that array to find the last occurrence of the rank and use that as the index.

let ranks = [];

// Find the next index at which the given rank can be
// inserted while maintaining the order of ranks
// See https://stackoverflow.com/a/21822316/1096470
function findNextIndex(rank) {
    var low = 0,
        high = ranks.length;

    while (low < high) {
        var mid = (low + high) >>> 1;
        if (ranks[mid] < rank) low = mid + 1;
        else high = mid;
    }
    return low;
}

function insert({Status, Rank, Name, Age}) {
    const html = `<tr>
        <td class="rankColumn">${Rank}</td>
        <td class="nameColumn">${Name}</td>
        <td class="ageColumn">${Age}</td>
    </tr>`;

    const index = findNextIndex(Rank);

    if (index === 0) {
        $(`#resultsTableBody`).prepend(html);
    } else {
        $(`#resultsTableBody > tr:nth-child(${index})`).after(html);
    }

    // Insert the given rank to the ranks array while keeping it sorted
    ranks.splice(index, 0, Rank);
}

Here's another fiddle.

Yoav Kadosh
  • 4,807
  • 4
  • 39
  • 56
  • Wont this append each row to end of the table, I would like to have it be added to correct position on insertion. – KB_Shayan Jun 01 '19 at 20:21
  • @shayan you're right, I misread your question, see my updated answer. – Yoav Kadosh Jun 01 '19 at 20:26
  • 1
    After some testings and comparisons between your script and mine, I found out yours performs better when inserting large amount of data (I tested with 1000 inserts and yours does it in average of 3 seconds when mine averages around 11 seconds), so I was wrong, my bad, I guess OP should accept your answer. – tcj Jun 02 '19 at 16:24
  • 1
    Thank you for your honesty @tcj. I think my answer can be improved because, as you've mentioned, it does rerender the whole list every time, which is an expensive operation. – Yoav Kadosh Jun 02 '19 at 19:00
1

So I've come up with another solution, you could look for the index in the DOM tree of the last rank's parent (<tr>) using the :contains selector function (and also .index() function) and then just use the .eq() as you tried (you had the good idea) in conjunction with the .after() or .before function to add the new item at the good place.

Please note that I've changed the classes of the titles in the <thead> to avoid confusing them with the actual data inserted.

function insert(data) {
    var dataToAppend = '<tr><td class="rankColumn">'+data[0].rank+'</td><td class="nameColumn">'+data[0].name+'</td><td class="ageColumn">'+data[0].age+'</td></tr>';
if ($('#resultsTableBody > tr').length == 0) {
    $('#resultsTableBody').append(dataToAppend);
} else {
    var arrayRanks = [];
    $('.rankColumn').each(function(i, val) {
        arrayRanks[$(val).parent().index()] = $(val).html();
    });
    arrayRanks.push(data[0].rank)
    arrayRanks.sort(function (a, b) {
        return a - b;
    });
    var index = arrayRanks.indexOf(data[0].rank);
    if (index == 0) {
        $('#resultsTableBody > tr').eq(index).before(dataToAppend);
    } else {
        $('#resultsTableBody > tr').eq(index-1).after(dataToAppend);
    }           
}

}

insert([{ rank: 1, name: 'John', age: 24 }]);
insert([{ rank: 2, name: 'Laura', age: 22 }]);
insert([{ rank: 3, name: 'Hannah', age: 31 }]);
insert([{ rank: 1, name: 'Misha', age: 27 }]);
insert([{ rank: 3, name: 'Paul', age: 43 }]);
insert([{ rank: 1, name: 'Bob', age: 19 }]);

Working example below :

function insert(data) {
 var dataToAppend = '<tr><td class="rankColumn">'+data[0].rank+'</td><td class="nameColumn">'+data[0].name+'</td><td class="ageColumn">'+data[0].age+'</td></tr>';
if ($('#resultsTableBody > tr').length == 0) {
    $('#resultsTableBody').append(dataToAppend);
} else {
 var arrayRanks = [];
 $('.rankColumn').each(function(i, val) {
  arrayRanks[$(val).parent().index()] = $(val).html();
 });
 arrayRanks.push(data[0].rank)
 arrayRanks.sort(function (a, b) {
  return a - b;
 });
 var index = arrayRanks.indexOf(data[0].rank);
 if (index == 0) {
  $('#resultsTableBody > tr').eq(index).before(dataToAppend);
 } else {
  $('#resultsTableBody > tr').eq(index-1).after(dataToAppend);
 }   
}
}

    insert([{ rank: 1, name: 'John', age: 24 }]);
    insert([{ rank: 2, name: 'Laura', age: 22 }]);
    insert([{ rank: 3, name: 'Hannah', age: 31 }]);
    insert([{ rank: 1, name: 'Misha', age: 27 }]);
    insert([{ rank: 3, name: 'Paul', age: 43 }]);
    insert([{ rank: 1, name: 'Bob', age: 19 }]);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table class="ResultsTable">
    <thead class="header">
        <tr>
            <td class="rankText">Rank</td>
            <td class="nameText">Name</td>
            <td class="ageText">Age</td>
        </tr>
    </thead>
    <tbody id="resultsTableBody"></tbody>
</table>
tcj
  • 1,645
  • 4
  • 13
  • 21
  • Would it be possible to explain how that sort function works, Does this add them to correct position on insertion? – KB_Shayan Jun 01 '19 at 20:23
  • Sure, it does a comparison between each ranks and selects the lowest one because of the result of the substraction. – tcj Jun 01 '19 at 20:30
  • I should have probably explained this but the data will all not be available when inserting they will be retrieved one at a time and it will find its place in the table based on the others. – KB_Shayan Jun 01 '19 at 20:41
  • 1
    But i'm gonna write another one that suits your needs, I'll edit when I'm done. – tcj Jun 01 '19 at 20:47
  • There seems to be a bug with this, If you were to switch insertion of Hannah and Laura into the table, it will not be in correct order at the end. Because same ranks always follow the first ranks position so it will always be 1,1,1,3,3,2,2,2 – KB_Shayan Jun 02 '19 at 11:16
  • 1
    Thanks for mentioning it, gonna have a look at it and fix it asap. – tcj Jun 02 '19 at 11:20
  • Code updated, please check it and tell me if you happen to find another bug. – tcj Jun 02 '19 at 12:42
  • Actually, it was still buggy, I re-updated the code with this time, I believe, a final and good one! But still let me know if you find out a bug. – tcj Jun 02 '19 at 14:06