Sorting dynamically generated Table Alphabetically - javascript

I'm trying to sort a table by alphabetical order. This is based on the 2nd column of each row (under the header 'Title'). My code works great when isolated in an html file. Located here...
The problem occurs when I try to make it work with a dynamically built table. I'll show the important parts of the code below.
sortTable function...
function sortTable() {
var table, rows, switching, i, x, y, shouldSwitch;
table = document.getElementById("manga-tracker").getElementsByTagName('table')[0];
switching = true;
/* Make a loop that will continue until
no switching has been done: */
while (switching) {
// Start by saying: no switching is done:
switching = false;
rows = table.getElementsByTagName("tr");
/* Loop through all table rows (except the
first, which contains table headers): */
for (var i = 1; i < (rows.length - 1); i++) {
// Start by saying there should be no switching:
shouldSwitch = false;
/* Get the two elements you want to compare,
one from current row and one from the next: */
x = rows[i].getElementsByTagName("td")[1].getElementsByTagName('a')[0].text;
y = rows[i + 1].getElementsByTagName("td")[1].getElementsByTagName('a')[0].text;
console.log('x: '+x+', y: '+y);
// Check if the two rows should switch place:
if (x.toLowerCase() > y.toLowerCase()) {
// If so, mark as a switch and break the loop:
shouldSwitch = true;
break;
}
}
if (shouldSwitch) {
/* If a switch has been marked, make the switch
and mark that a switch has been done: */
rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
switching = true;
}
}
}
This is what creates the table by receiving data from server. The HTML produced by this function looks just like the HTML I supplied in the codepen example at the top of this post.
function populateMangaTracker() {
$('#manga-tracker').html("<table><th> </th><th>Title</th><th>Released</th><th>Owned</th><th>Read</th><th>Last Updated</th><th>Posted By</th></table>");
$.ajax({
method: 'GET',
url: 'populate-mangatracker.php',
});
$.getJSON('populate-mangatracker.php', function(data){
$.each( data, function( key, val ) {
$('#manga-tracker table').append("<tr><td><input type='checkbox' name='delete-manga' value='"+val.id+"' /></td><td><a href='?id="+val.id+"'>"+val.title+"</a></td><td>"+val.total+"</td><td>"+val.owned+"</td><td>"+val.volRead+"</td><td>"+val.lastUpdated+"</td><td>"+ val.owner.charAt(0).toUpperCase() + val.owner.substr(1) +"</td></tr>");
});
// add onclick event to #manga-tracker links
$('#manga-tracker a').on('click', function(e) {
e.preventDefault();
e.stopPropagation();
var pathName = $(this).attr('href').split('=');
pathName = pathName[1];
if(pathName >= 0) {
window.location = 'edit-manga.php?id='+pathName;
}
});
});
}
Inside of the tableSort() function... When I try to console.log() inside of the for() loop it doesn't work because the variable 'rows' only has a length of 1 therefore anything past index 1 is unknown.
When I console.log() 'rows' before the for() loop, it contains all 10 of the tr tags. I don't understand where the contents of the array are going... Can someone help me get this working?
Please and thank you!

If you're OK with removing the table elements and adding them again, try this:
const tbody = document.querySelector('#manga-tracker tbody');
const [trhead, ...trs] = [...tbody.children];
trs.sort((tr1, tr2) => tr1.children[1].textContent.localeCompare(tr2.children[1].textContent));
tbody.textContent = '';
[trhead, ...trs].forEach(tr => tbody.appendChild(tr));
<div id="manga-tracker-wrapper">
<form action="delete-manga.php" method="POST">
<div id="manga-tracker">
<table>
<tbody>
<tr>
<th> </th>
<th>Title</th>
<th>Released</th>
<th>Owned</th>
<th>Read</th>
<th>Last Updated</th>
</tr>
<tr>
<td><input type="checkbox" name="delete-manga" value="7"></td>
<td>Arifureta: From Commonplace to World's Strongest</td>
<td>20</td>
<td>1</td>
<td>1</td>
<td>2018-05-14</td>
</tr>
<tr>
<td><input type="checkbox" name="delete-manga" value="6"></td>
<td>Akame ga KILL!</td>
<td>14</td>
<td>14</td>
<td>14</td>
<td>2018-05-13</td>
</tr>
<tr>
<td><input type="checkbox" name="delete-manga" value="11"></td>
<td>Dragonball Super</td>
<td>2</td>
<td>1</td>
<td>1</td>
<td>2018-05-14</td>
</tr>
<tr>
<td><input type="checkbox" name="delete-manga" value="12"></td>
<td>7th Garden</td>
<td>8</td>
<td>1</td>
<td>1</td>
<td>2018-05-14</td>
</tr>
<tr>
<td><input type="checkbox" name="delete-manga" value="13"></td>
<td>Attack on Titan</td>
<td>25</td>
<td>2</td>
<td>0</td>
<td>2018-05-14</td>
</tr>
<tr>
<td><input type="checkbox" name="delete-manga" value="14"></td>
<td>The Ancient Magus' Bride</td>
<td>8</td>
<td>7</td>
<td>7</td>
<td>2018-05-14</td>
</tr>
<tr>
<td><input type="checkbox" name="delete-manga" value="15"></td>
<td>Beasts of Abigaile</td>
<td>3</td>
<td>3</td>
<td>3</td>
<td>2018-05-14</td>
</tr>
<tr>
<td><input type="checkbox" name="delete-manga" value="16"></td>
<td>Berserk</td>
<td>39</td>
<td>6</td>
<td>6</td>
<td>2018-05-14</td>
</tr>
<tr>
<td><input type="checkbox" name="delete-manga" value="17"></td>
<td>Fairy Tale</td>
<td>63</td>
<td>1</td>
<td>0</td>
<td>2018-05-14</td>
</tr>
</tbody>
</table>
</div>
</form>
<div id="delete-response">
</div>
</div>

I tried #certainperformances solution. Although it would work when isolated in a separate file, it wouldn't work in my project. Then I thought about what #certainperformances suggested in terms of just storing the data and not the elements.It hit me. I could just sort the data before I output it to the table. I was just complicating things.
function populateMangaTracker() {
var toBeSorted = [];
$('#manga-tracker').html("<table><th> </th><th>Title</th><th>Released</th><th>Owned</th><th>Read</th><th>Last Updated</th><th>Posted By</th></table>");
$.ajax({
method: 'GET',
url: 'populate-mangatracker.php',
});
$.getJSON('populate-mangatracker.php', function(data){
$.each( data, function( key, val ) {
/* Stored data in an array */
toBeSorted[key] = {
'id': val.id,
'title': val.title,
'total': val.total,
'owned': val.owned,
'read': val.volRead,
'lastUpdated': val.lastUpdated,
'owned': val.owner.charAt(0).toUpperCase() + val.owner.substr(1)
};
});
/* Then sorted alphabetically here */
toBeSorted.sort(sortObject); // sort alphabetically
/* And outputted data to table here */
for(var i = 0; i < toBeSorted.length; i++) {
$('#manga-tracker table').append("<tr><td><input type='checkbox' name='delete-manga' value='"+toBeSorted[i].id+"' /></td><td><a href='?id="+toBeSorted[i].id+"'>"+toBeSorted[i].title+"</a></td><td>"+toBeSorted[i].total+"</td><td>"+toBeSorted[i].owned+"</td><td>"+toBeSorted[i].volRead+"</td><td>"+toBeSorted[i].lastUpdated+"</td><td>"+toBeSorted[i].owner+"</td></tr>");
}
});
}

Related

Sort checkbox(checked and unchecked) in table

I want to sort my checkbox when i click on the X:
JS to sort my checkbox(checked and unchecked)?
I got no idea how to write it. please help.
The following code is borrowed.
The Price and stock value will be pass from other JS file using router.
But for now I make it simple because I want to know how to sort the checkbox.
var sortedPrice = false;
function sortPrice() {
$('#myTable').append(
$('#myTable').find('tr.item').sort(function (a, b) {
var td_a = $($(a).find('td.sortPrice')[0]);
var td_b = $($(b).find('td.sortPrice')[0]);
if(sortedPrice){
if(td_a.html() == 'Free') return -1;
return td_b.html().replace(/\D/g, '') - td_a.html().replace(/\D/g, '');
}else{
if(td_a.html() == 'Free') return 1;
return td_a.html().replace(/\D/g, '') - td_b.html().replace(/\D/g, '');
}
})
);
if(sortedPrice) sortedPrice = false;
else sortedPrice = true;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table class="table" id="myTable">
<tr>
<th onclick="sortPrice()">Price</th>
<th>Stock</th>
<th>%</th>
<th>X</th>
</tr>
<tr class="item">
<td class="sortPrice">1</td>
<td>1</td>
<td>2</td>
<td><input type="checkbox" value="1"></td>
</tr>
<tr class="item">
<td class="sortPrice">4</td>
<td>3</td>
<td>1</td>
<td><input type="checkbox" value="2"></td>
</tr>
<tr class="item">
<td class="sortPrice">7</td>
<td>4</td>
<td>6</td>
<td><input type="checkbox" value="3"></td>
</tr>
<tr class="item">
<td class="sortPrice">2</td>
<td>7</td>
<td>8</td>
<td><input type="checkbox" value="4"></td>
</tr>
<tr class="item">
<td class="sortPrice">3</td>
<td>4</td>
<td>2</td>
<td><input type="checkbox" value="5"></td>
</tr>
</table>
I would try to make the click handler generic by taking the following steps:
Create a function that takes an array of pairs, and sorts that array by the first value in every pair, and returns the sorted array with just the second value from each pair in sorted order. This generic function can be used to pass pairs of cell-content and corresponding row element. This function could also take care of reversing the order when the input pairs were already sorted.
Create a single click handler for the td elements (the column headers). Let it collect the cells in the corresponding column, and for each cell determine whether the checkbox state should be taken as value, or the text content of that cell.
After sorting the values in the column with the first function, the rows can be fed into the table again.
Use the compare function from Intl.Collator so to have numeric sort when appropriate.
This way you can do away with some of the HTML (onclick, sortPrice, item, ...)
const {compare} = new Intl.Collator(undefined, {numeric: true});
function sortSecondByFirst(pairs) {
const sorted = [...pairs].sort(([a], [b]) => compare(a, b))
.map(([,a]) => a);
if (pairs.every(([,a], i) => a === sorted[i])) {
sorted.reverse(); // Was already sorted
}
return sorted;
}
$("th", "#myTable").click(function () {
sortColumn($(this).index());
});
function sortColumn(colIdx) {
const $cells = $(`tr > td:nth-child(${colIdx+1})`, "#myTable");
$("#myTable").append(
sortSecondByFirst($cells.get().map((cell) => {
const $input = $('input[type=checkbox]', cell);
const value = $input.length ? $input.prop("checked") : $(cell).text();
return [
value,
$(cell).parent()
];
}))
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table id="myTable">
<tr>
<th>Price</th><th>Stock</th><th>%</th><th>X</th>
</tr>
<tr>
<td>1</td><td>1</td><td>2</td>
<td><input type="checkbox" value="1"></td>
</tr>
<tr>
<td>4</td><td>3</td><td>1</td>
<td><input type="checkbox" value="2"></td>
</tr>
<tr>
<td>7</td><td>4</td><td>6</td>
<td><input type="checkbox" value="3"></td>
</tr>
<tr>
<td>20</td><td>7</td><td>8</td>
<td><input type="checkbox" value="4"></td>
</tr>
<tr>
<td>3</td><td>4</td><td>2</td>
<td><input type="checkbox" value="5"></td>
</tr>
</table>
Quite honestly if u have a choice I'd always go use Vue, react or the like as a ui framework. There this is simpler and u have a better -in my eyes - split of html template and data. Vue is quite easy to learn from my experience too.(great tutorials eg on YouTube)
That said in jQuery I guess I would write a sort function like the one u got there that via onclick event it triggered when X is clicked on and for the sorting write a similar compare function as above. Eg
(a,b) => a.checked - b.checked;
Hope this makes sense to you or where precisely do u struggle?

How to remove TR if there is a specific text inside its TD's in jquery?

I want to remove the TR if its 2nd TD value is similar to another TRs TD value and it's last TD value shouldn't be HIT. And the another scenario is if I have 3 TRs with the same data then 2 of them should be removed and 1 should remain there.
Example:
<table>
<tr>
<td>ID</td>
<td>Ref No</td>
<td>Name</td>
<td>Result</td>
</tr>
<tr>
<td>1</td>
<td>1121</td>
<td>Joseph</td>
<td>CLEAR</td>
</tr>
<tr>
<td>2</td>
<td>1122</td>
<td>Mike</td>
<td>CLEAR</td>
</tr>
<tr>
<td>3</td>
<td>1122</td>
<td>Mike</td>
<td>CLEAR</td>
</tr>
<tr>
<td>4</td>
<td>1122</td>
<td>Mike</td>
<td>HIT</td>
</tr>
<tr>
<td>5</td>
<td>1123</td>
<td>Jim</td>
<td>HIT</td>
</tr>
<tr>
<td>6</td>
<td>1124</td>
<td>James</td>
<td>CLEAR</td>
</tr>
<tr>
<td>7</td>
<td>1124</td>
<td>James</td>
<td>CLEAR</td>
</tr>
<tr>
<td>8</td>
<td>1124</td>
<td>James</td>
<td>CLEAR</td>
</tr>
</table>
What I want:
<table>
<tr>
<td>ID</td>
<td>Ref No</td>
<td>Name</td>
<td>Result</td>
</tr>
<tr>
<td>1</td>
<td>1121</td>
<td>Joseph</td>
<td>CLEAR</td>
</tr>
<tr>
<td>4</td>
<td>1122</td>
<td>Mike</td>
<td>HIT</td>
</tr>
<tr>
<td>5</td>
<td>1123</td>
<td>Jim</td>
<td>HIT</td>
</tr>
<tr>
<td>6</td>
<td>1124</td>
<td>James</td>
<td>CLEAR</td>
</tr>
</table>
Can anybody tell me how to achieve this task?
Any help would be highly appreciated.
So i made this clumsy answer for you. You can check it out in the fiddle here.
EDIT: after some discussion about what should the behaviour be, i updated the fiddle. so now it adds the check if there are any fields in the duplicates that have a "HIT" value in fourth column it will keep the first row with HIT value, otherwise it will keep the first value for each unique second column value.
I am sure there is a better/simpler/more effective way to do this with jQuery, but that is what I came up with. The basic algorithm is this: get all rows and iterate. For each row: find the value in second td (column), check all subsequent rows, fetch the value in second column there and compare them. if they are the same, remove the duplicate row from DOM.
//get the table rows, this should be done with a different selector if there are more tables e.g. with class or id...
$tableRows = $("tr");
//iterate over all elements (rows)
$tableRows.each(function(index, element) {
var $element = $(element);
//get the value of the current element
var currentRowValue = $element.find("td:nth-child(2)").text();
//check all elements that come after the current element if the value matches, if so, remove the matching element
for (var i = index + 1; i < $tableRows.length; i++) {
var $rowToCompare = $($tableRows[i]);
var valueToCompare = $rowToCompare.find("td:nth-child(2)").text();
if(valueToCompare === currentRowValue) {
//remove the duplicate from dom
//if the second row (the duplicate) has 4th column of "HIT" then keep the second row and remove the first row
var duplicateRowFourthColumnVal = $rowToCompare.find("td:nth-child(4)").text();
if(duplicateRowFourthColumnVal == "HIT") {
$element.remove();
}
else {
$rowToCompare.remove();
}
}
}
});`

jqQuery find cell value, if exists change cell, if not add new row

Any help would be greatly appreciated.
What I'm trying to achieve is when a number/text in input into the code box it searches the table, if found increments quantity by one, if not found adds a new row counting the no column by one.
I already a some basic jQuery code.
<input type="text" style="width: 200px" id="code" name="code" />
<input id = "btnSubmit" type="submit" value="Release"/>
<table> <thead>
<tr>
<th>No</th>
<th>Code</th>
<th>Qty</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>4444</td>
<td>1</td>
</tr>
<tr>
<td>2</td>
<td>5555</td>
<td>1</td>
</tr>
<tr>
<td>3</td>
<td>6666</td>
<td>1</td>
</tr>
<tr>
<td>4</td>
<td>7777</td>
<td>1</td>
</tr>
</tbody>
</table>
edit: my code.
$(document).ready(function() {
$("#btnSubmit").click(function() {
var code = $("input#code").val()
var table = $("table tbody");
table.find('tr').each(function(i) {
no = $(this).find('td').eq(0).text(),
productId = $(this).find('td').eq(1).text(),
Quantity = $(this).find('td').eq(2).text();
if (productId == code) { //see if product is in table
Quantity = +Quantity + +Quantity; // increase qty
alert('found' + Quantity); 
} else {
// Add new row
alert('not found');
}
});
});
});
I put together a JSFiddle for you, and copied the JS code here. I tried to make it as beginner friendly as possible...
$("#btnSubmit").on("click", function(){
numRows = $("tr").length;
for(var i=1 ; i<numRows ; i++){
var code = $("tr:nth-child(" + i + ") td:nth-child(2)").html();
var qty = $("tr:nth-child(" + i + ") td:nth-child(3)").html();
if(code == $("#code").val()){
$("tr:nth-child(" + i + ") td:nth-child(3)").html(parseInt(qty) + 1);
return true;
}
}
$("tbody").append("<tr><td>" + numRows + "</td><td>" + $("#code").val() + "</td><td>1</td></tr>");
return true;
});
I have created a sample code using jQuery. It took me like 10 minutes to figure out what you are trying to achive but I hope I understood you quite well:
HTML Side:
<input type="text" style="width: 200px" id="code" name="code" />
<input id = "btnSubmit" type="submit" value="Release"/>
<table> <thead>
<tr>
<th>No</th>
<th>Code</th>
<th>Qty</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>4444</td>
<td>1</td>
</tr>
<tr>
<td>2</td>
<td>5555</td>
<td>1</td>
</tr>
<tr>
<td>3</td>
<td>6666</td>
<td>1</td>
</tr>
<tr>
<td>4</td>
<td>7777</td>
<td>1</td>
</tr>
</tbody>
</table>
and our JavaScript:
$(document).ready(function() {
var found = false;
$("input#btnSubmit").on("click", function() {
var search_val = $("input#code").val();
$("tr").each(function() {
var obj = $(this);
obj.find("td").each(function() {
if(parseInt($(this).html()) == parseInt(search_val))
{
obj.find("td:nth-of-type(3)").html(parseInt(obj.find("td:nth-of-type(3)").html()) + 1);
found = true;
}
});
})
if(found == false)
{
$("table").append("<tr><td>"+($("tr").length)+"</td><td>"+search_val+"</td><td>1</td></tr>");
}
found = false;
});
});
Here's JSFiddle: http://jsfiddle.net/f17gudfw/4/

match words that appear at the beginning of the line in a table cell

Please take a look at this FIDDLE. How would you make sure it only matches the occurrence of Sodium that appear at the beginning of the line in a table cell, for example :
<td>Sodium</td>, <td>Sodium (from Kitchen Salt)</td>
but not
<td>Vitamin sodium</td>,<td>Fish Sodium</td>
My attempt
`var find_Sodium = /^Sodium/
alert($('.'+title+'table').find('td:contains(find_Sodium)').next().html());`
isn't working.
$.ajax({
url: "url.json",
success: function (data) {
$(data.query.results.json.json).each(function (index, item) {
var title = item.title;
var table = item.table;
if (table.indexOf("Sodium") >= 0) {
$('.'+ title+'table').html(''+table+'');
var find_Sodium = /^Sodium/;
alert($('.'+title+'table').find('td:contains(find_Sodium)').next().html());
}
});
},
error: function () {}
});
Table Structure:
<table class="tablesorter">
<thead>
<tr>
<td>Ingredient</td>
<td>Amount</td>
<td>% Daily Value**</td>
</tr>
</thead>
<tbody>
<tr>
<td>Calories</td>
<td>10</td>
<td></td>
</tr>
<tr>
<td>Sodium</td>
<td>2g</td>
<td><1</td>
</tr>
<tr>
<td>Vitamin C</td>
<td>110mg</td>
<td>4</td>
</tr>
<tr>
<td>Potassium sodium</td>
<td>235mg</td>
<td>6</td>
</tr>
<tr>
<td>Omega 6</td>
<td>1100mg</td>
<td>*</td>
</tr>
<tr>
<td>Vitamin Sodium</td>
<td>1200mg</td>
<td>*</td>
</tr>
<tr>
<td>Vitamin E</td>
<td>300mg</td>
<td>*</td>
</tr>
</tbody>
</table>
:contains does not accept a regex, the way to do this is to filter()
$('.'+title+'table').find('td').filter(function() {
return $(this).text().indexOf('Sodium') === 0;
}).next().html();
FIDDLE
using indexOf === 0 makes sure Sodium has an index of zero, being the first thing to occur in the elements text

How to extract text inside nested HTML using JQuery?

I have here HTML Code:
<div class="actResult" style="border: solid">
<table>
<tbody>
<tr>
<td>Order Number</td>
<td>1</td>
</tr>
<tr>
<td>Customer Number</td>
<td>3</td>
</tr>
<tr>
<td>Complaint Code</td>
<td>b</td>
</tr>
<tr>
<td>Receivable Receipt Number</td>
<td>5</td>
</tr>
<tr>
<td>Date Called</td>
<td>2014-03-19</td>
</tr>
<tr>
<td>Scheduled Day Of Checkup</td>
<td>2014-03-19</td>
</tr>
<tr>
<td>Scheduled Day Of Service</td>
<td>2014-03-21</td>
</tr>
<tr>
<td>Checkup Status</td>
<td>Y</td>
</tr>
<tr>
<td>Service Status</td>
<td>N</td>
</tr>
<tr>
<td>Technician Number Checkup</td>
<td>3</td>
</tr>
<tr>
<td>Technician Number Service</td>
<td>1</td>
</tr>
</tbody>
</table>
</div>
I want to get the values of the tags and put them into an array with the a structure like array("first td" => "second td"), so for this case the array would be array("Order Number" => "1", "Customer Number" => "3", "Complaint Code" => "b", ...) and so on.
After that, the final array would be sent into a PHP code.
I've been trying to extract some of the values from the HTML using var html = $(this).filter(function( index ){ return $("td", this) }).filter(":odd").text(); and various other combinations of filter(), but it doesn't seem to work for me.
How do I go about doing what I want to do?
jsFiddle Demo
You are going to want to use .each for that and iterate through the rows in the table. For each row, take the first cell (.eq(0)) as the key, and the second cell (.eq(1)) as the value. Place these in a result object.
//object to hold resulting data
var result = {};
//iterate through rows
$('.actResult tr').each(function(){
//get collection of cells
var $tds = $(this).find('td');
//set the key in result to the first cell, and the value to the second cell
result[$tds.eq(0).html()] = $tds.eq(1).text();
});
You can get the rows property of the table element and create an object based on the cells' value:
var rows = document.querySelector('.actResult table').rows,
data = {}, c, l = rows.length, i = 0;
for (; i < l; i++) {
c = rows[i].cells;
data[c[0].innerHTML] = c[1].innerHTML;
}
http://jsfiddle.net/tG8F6/

Categories