KB_Shayan
KB_Shayan

Reputation: 652

How to Insert a Row to a Sorted Table While Maintaining Order

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.

Upvotes: 0

Views: 1234

Answers (2)

Yoav Kadosh
Yoav Kadosh

Reputation: 5155

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.

Upvotes: 2

tcj
tcj

Reputation: 1675

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>

Upvotes: 1

Related Questions