Sorting table using only Javascript - javascript

I have this table in which I read the tbody contents from a JSON API and now I need to make it sortable by columns and using only javascript and no Jquery.
Any help would be appreciated
i have found this code which is exactly what i want, but i don't know how to refer to tbodies from my api
var myRequest = new XMLHttpRequest();
myRequest.open('GET', 'https://jsonplaceholder.typicode.com/todos');
myRequest.onload = function () {
var myData = JSON.parse(myRequest.responseText);
dataTable(myData);
};
myRequest.send();
function dataTable(data) {
if (data.length > 0) {
var temp = '';
data.forEach((u) => {
temp += '<tr>';
temp += "<td style='text-align: center'>" + u.userId + '</td>';
temp += "<td style='text-align: center'>" + u.id + '</td>';
temp += '<td>' + u.title + '</td>';
temp += "<td style='text-align: center'>" + u.completed + '</td></tr>';
document.getElementById('data').innerHTML = temp;
});
}
}
<table class="table_id">
<thead>
<tr>
<th>UserID</th>
<th>ID</th>
<th>Title</th>
<th>Completion</th>
</tr>
</thead>
<tbody id="data">
</tbody>
</table>

var myData, asc = {'userId':true, 'id':true, 'title':true, 'completed':true};
var myRequest = new XMLHttpRequest();
myRequest.open('GET', 'https://jsonplaceholder.typicode.com/todos');
myRequest.onload = function () {
myData = JSON.parse(myRequest.responseText);
dataTable(myData);
};
myRequest.send();
function sortTable(key){
myData.sort(function(a, b) {
if(asc[key]){
return a[key] > b[key]? 1:-1;
}
else{
return a[key] > b[key]? -1:1;;
}
});
asc[key] = !asc[key];
document.getElementById('data').innerHTML = '';
dataTable(myData);
}
function dataTable(data) {
if (data.length > 0) {
var temp = '';
data.forEach((u) => {
temp += '<tr>';
temp += "<td style='text-align: center'>" + u.userId + '</td>';
temp += "<td style='text-align: center'>" + u.id + '</td>';
temp += '<td>' + u.title + '</td>';
temp += "<td style='text-align: center'>" + u.completed + '</td></tr>';
document.getElementById('data').innerHTML = temp;
});
}
}
<table class="table_id">
<thead>
<tr>
<th onclick="sortTable('userId');">UserID</th>
<th onclick="sortTable('id');">ID</th>
<th onclick="sortTable('title');">Title</th>
<th onclick="sortTable('completed');">Completion</th>
</tr>
</thead>
<tbody id="data">
</tbody>
</table>

Here is a quick class I made.
You first load your data with dt.load(), then when someone clicks one of the headers, you can add an event that does:
elm.addEventListener("click", (e) => {
prop = e.target.textContent.trim();
dt.sort(prop);
dt.render();
})
class DataTable{
load(arr){
if(arr) this.data = arr;
}
sort(prop){
this.data.sort((a, b) => a[prop] - b[prop]);
}
render(selector="#data"){
if(data.length){
html = data.map(u => {
return [
"<tr>",
`<td style='text-align: center'>${u.userId}</td>`,
`<td style='text-align: center'>${u.id}</td>`,
`<td>${u.title}</td>`,
`<td style='text-align: center'>${u.completed}</td></tr>`
].join("");
}).join("");
document.querySelector(selector).innerHTML = html;
}
}
}

Important - are you wanting to be able to sort multiple columns at the same time or just one column at a time?
Initialize myData outside of onload first. You'll want to be able to access those results outside of onload to sort them. The actual sort function JS offers is a pretty confusing but it's really the only way to go about vanilla JS array sorting.
function sortData(col, asc = true) {
//JS sort function
myData = myData.sort((first, second) => {
//sort logic
if (first[col] == second[col]) {
return 0;
}
if (asc) {
if (first[col] > second[col]) {
return 1;
}
else {
return -1;
}
}
else {
if (first[col] > second[col]) {
return -1;
}
else {
return 1;
}
}
});
//Re-Create table
dataTable(myData);
}
EDIT:
I added in sort logic, but it's definitely possible I messed up. I can't actually test this right now and I haven't touched the sort function in years.

Related

Highlight Searched Text in Table bug

So I'm working on a feature that require a user to type in a search item that will search a table for the term and highlight every occurrence. I found some code and got it to work, but I have a bug that I'm not sure how to resolve. In the search input if I hit back space to erase the search term, the unhighlight works until the last character letter. For example, I type in Demo and use back space to delete Demo, only the D is still highlighted.
Below are my code snippets.
NOTE: I have a search and render function that call the highlightActivity and renderReports functions
My CSS
.highlight {
background-color :gold
}
.highlight {
padding:1px 4px;
margin: 0 -4px;
}
My HTML
<input type="text" id="uniStringSearch" onkeyup="SearchAndRender()"></input>
<table id="currentReports" class="table table-striped table-condensed table-bordered">
<thead>
<tr>
<th><input name="select_all" id="selectedReports" value="1" type="checkbox" onclick="checkAll(this)" /> </th>
<th>DATE</th>
<th>SUMMARY TITLE</th>
<th>NAME</th>
<th>ACTIVITY TYPE</th>
<th>SUB ACTIVITY TYPE</th>
<th>ACTIVITY DETAILS</th>
</tr>
</thead>
<tbody id="reports"></tbody>
</table>
HighlightActivity Function
function highlightActivity() {
var searchTerm = $('#uniStringSearch').val();
var tableID = ''
switch (webPage) {
case 'reports':
tableID = '#currentReports';
break;
case 'analysis':
tableID = '#currentAnalysis';
break;
case 'statuses':
tableID = '#currentStatuses';
break;
default:
}
$(tableID).find('.highlight').removeClass('highlight');
$(tableID).find('tr td').each(function () {
if ((this).attr('data-search') !== 'false') {
var text = $(this).text();
var textLowerCase = text.toLowerCase();
var pos = textLowerCase.indexOf(searchTerm.toLowerCase());
var regex = new RegExp(searchTerm, 'ig');
text = text.replace(regex, (match, $1) => {
return '<span class="highlight">' + match + '</span>';
});
$(this).html(text);
if (pos !== -1) {
setTimeout(function () {
if ($(this).parent().find('.highlight').is(':empty')) {
$('.highlight').remove();
}
}.bind(this), 0);
}
else {
$(this).text(text);
}
}
if ($(this).parent().find('.highlight').length > 0) {
$(this).parent().show();
}
else {
$(this).parent().hide();
}
});
}
Render Reports Function
function renderReports(items) {
var html = '';
for (var i = 0; i < items.length; i++)
{
html = '<tr><td><input type="checkbox" id="selectedReports" onchange="chkInputChanged(event)"/></td>'
html += '<td>' + items[i].DateActivity + '</td>'
html += '<td>' + items[i].Name + '</td>'
html += '<td>' + items[i].ActivityType + '</td>'
html += '<td>' + items[i].SubActivityType + '</td>'
html += '<td id="searchActivityDetails">' + items[i].ActivityDetails + '</td></tr>'
}
$('#reports').empty();
$('#reports').append(html);
}
ALSO....one more thing, is there a way to have my highlight function to target a specific column? For example Summary Title and Activity Details

Add button to a dynamically created table row

so Iam trying to add this button to a dynamically created table row. But I honestly don't know how to get this working. The code is a mess, because Iam trying so many things in order to get this going.
I need to append this button (element) to the table row where the function is called, is there any smart way to do this. If i change my structure of the table, thats okay.
function getPerson(){
//addButton.value = "+";
//addButton.class = "btn btn-success";
$("#output1 tbody tr").remove();
fetch(url)
.then(res => res.json())
.then(data => {
console.log(data);
var temp = "";
data.forEach((person) => {
temp += "<tr>";
temp += "<td>" +person.id+"</td>";
console.log(person.id);
temp += "<td>" +person.vorname+"</td>";
temp += "<td>" +person.nachname+"</td>";
temp += "<td>"
person.projectList.forEach((project) => {
temp += project.name+"<br>";
});
temp += "</td>"
temp += "<td>"
person.technoList.forEach((techno) => {
temp += techno.name+"<br>";
});
temp += "</td>"
temp += "<td id='addButton" + x + "' >" + addButton(person.id)+ "</td>"
//this.parentElement.appendChild(this)
x++;
//document.getElementById('personDetail_Button').addEventListener('click', this.personDetail(person.id));
temp += "</tr>"
});
document.getElementById("tableOutput").innerHTML= temp;
});
}
function addButton(person_id){
var person_id_onclick = person_id
var element = document.createElement("input");
element.type = 'button';
element.value = 'test123';
element.onclick = function(){
personDetail(person_id_onclick);
}
$(element).addClass('btn btn-primary');
//$(this).appendChild(element);
document.body.appendChild(element);
}
<table class="table" id="output1">
<thead class="thead-dark">
<tr>
<th scope="">ID</th>
<th scope="col">Vorname</th>
<th scope="col">Nachname</th>
<th scope="col">Projekte</th>
<th scope="col">Technologien</th>
<th scope="col">Button</th>
</tr>
</thead>
<tbody id="tableOutput">
</tbody>
</table>
You can try replacing document.body.appendChild(element); with return element; I also updated the function to remove the random jquery class.
function addButton(person_id){
var element = document.createElement("input");
element.type = 'button';
element.value = 'test123';
element.setAttribute("data-person_id",person_id);
element.setAttribute("class","btn-viewdetail btn btn-primary");
var _wrap = document.createElement("div");
_wrap.appendChild(element);
return _wrap.innerHTML;
}
var _btns = document.querySelectorAll(".btn-viewdetail");
_btns.forEach(function(el){
el.addEventListener("click",function(evt){
personDetail(evt.target.getAttribute("data-person_id"));
});
});
document.body.innerHTML += addButton(4);

Using Firestore's snapshot.forEach to create a table

I'm trying to output an array filled with Firestore objects onto a table, but just displays the last object above the table
<table class="darkTable">
<thead>
<tr>
<th>List of Available Shows</th>
</tr>
</thead>
<tbody>
<tr>
<div id="showList"></div>
</tr>
</tbody>
</table>
<script>
firebase.firestore().collection('TV Shows').get().then(snapshot => {
var i = 0;
var array = [];
snapshot.forEach(doc => {
array[i] = doc.data().show.name;
//console.log(doc.data().show.name);
//showList.innerHTML = array[i] + "<br />";
showList.innerHTML = '<td>' + array[i] + '</td>';
i++;
});
});
</script>
Is it the way I'm going about the td code lines?
assuming this markup:
<div id="showList"></div>
then it works about like this:
firebase.firestore().collection('TV Shows').get().then(snapshot => {
var showList = document.getElementById('showList');
var html = '<table class="darkTable"><thead><tr>';
html += '<th>List of Available Shows</th>';
/* add further columns into here, alike the one above. */
html += '</tr></thead><tbody>';
snapshot.forEach(doc => {
html += '<tr>';
html += '<td>' + doc.data().show.name + '</td>';
/* add further columns into here, alike the one above. */
html += '</tr>';
});
html += '</tbody></table>';
showList.append(html);
});
You're resetting the entire showList element with every iteration of the loop:
showList.innerHTML = '<td>' + array[i] + '</td>';
I suspect you mean to append to it each time instead or resetting it entirely each time. Maybe try building a string with each iteration, then set the whole thing after the loop is over.

Converting JSON array in HTML table using JQuery

My JSON Array containing date and key-value pairs of alphabets. I need columns as date values and rows heading as Alphabets.
{
"error":0,
"data":[
{
"date":"2017-12-01",
"A":1,
"B":2
},
{
"date":"2017-12-02",
"A":2,
"B":3
}
]
}
I want to create table as given below
Alpha 2017-12-01 2017-12-02
A 1 2
B 2 3
My HTML Code containing datatable for table formatting:
<table id="report" class="table table-striped table-bordered">
<thead>
<tr>
<th>Alpha</th>
</tr>
</thead>
<tbody></tbody>
</table>
And JQuery ajax get response that calls the API:
$.ajax({
url: 'userData/01/2018',
success: function(response) {
let reportData = response.data;
let i = 0;
let j = 1;
let k = 0;
let table = document.getElementById('report');
let tr = table.tHead.children[0];
reportData.forEach(function(data) {
let row = table.insertRow(j);
if (i == 0) {
let th = document.createElement('th');
th.innerHTML = data.date;
tr.appendChild(th);
}
if (k == 0) {
let keys = Object.keys(data);
for (let p = 1; p < keys.length; p++) {
let cell = row.insertCell(k);
cell.innerHTML = keys[p];
for (let q = 1; q < keys.length; q++) {}
}
}
});
}
});
I am able to insert headers as table columns but facing an issue in data insertion.
slight changes in your json string,
HTML:
<table id="report"></table>
JavaScript:
var jsonString = '{"error": 0,"Alpha": [{"date": "2017-12-01","A": 1,"B": 2},{"date": "2017-12-02","A": 2,"B": 3}]}';
var s = '';
$.each(JSON.parse(jsonString), function(i, j) {
if (i == 'Alpha') {
s += '<thead><th>' + i + '</th>';
$.each(j, function(k, val) {
s += '<th>' + val.date + '</th>';
});
s += '</thead>';
$('#report').html(s);
for (var l = 0; j.length; l++) {
if (l == 0) {
s = '<tbody><tr><td> ' + Object.keys(j[l])[l + 1] + ' </td>';
s += '<td> ' + j[l].A + ' </td><td>' + j[l].B + '</td></tr>';
$('#report').append(s);
} else {
s = '<tr><td>' + Object.keys(j[l])[l + 1] + '</td><td>' + j[l].A + '</td><td>' + j[l].B + '</td></tr>';
$('#report').append(s);
}
s += '</tbody>';
}
}
});
For reference - https://jsfiddle.net/zvxqf9mz/

Update totals in a table

I have:
$('#createStockOrder').click(function () {
modal('create-stock-order', {}, function () {
var $modal = $(this);
var submitted = false;
var model = [];
$('.glyphicon-plus').click(function () {
var product_id = $('#productSelect option:selected').text(),
size_id = $('#sizeSelect option:selected').text(),
colour_id = $('#colourSelect option:selected').text(),
quantity = $('#quantity').val();
// Get index of the element where all the fields matches
var index = getObjectIndex(model, product_id, size_id, colour_id);
// If object found in the array
if (index !== false) {
// Update the quantity in the same element
model[index].quantity = quantity;
} else {
// Add the element in the array
model.push({
product_id: product_id,
size_id: size_id,
colour_id: colour_id,
quantity: quantity
});
}
printStock(model);
});
var form = document.getElementById('create_sale');
var $form = $(form);
$form.on('submit', function (e) {
e.preventDefault();
if (!submitted) {
submitted = true;
$('#create_sale .btn-primary').addClass('disabled');
var formData = new FormData(form);
qwest.post(form.action, formData)
.then(function (resp) {
$modal.modal('hide');
})
.catch(function (xhr, response, e) {
var html = '';
$.each(response, function (i, v) {
html += '<p>' + v + '</p>';
});
$('#create_sale .alert').html(html).removeClass('hide');
$('#create_sale .btn-primary').removeClass('disabled');
submitted = false;
});
}
})
}, {width: 1000});
});
// Currently the function is Static, but it can be changed to dynamic
// by using nested loop and a flag to store the match status
function getObjectIndex(arr, product_id, size_id, colour_id) {
// Loop over array to find the matching element/object
for (var i = 0; i < arr.length; i++) {
var obj = arr[i];
if (obj.product_id === product_id && obj.size_id === size_id && obj.colour_id === colour_id) {
// When all key-value matches return the array index
return i;
}
}
// When no match found, return false
return false;
}
function printStock(model) {
var html = '';
var total_row_quantity = 0;
var total_row_value = 0;
$.each(model, function (i1, v1) {
html += '<tr>';
$.each(v1, function (i2, v2) {
html += '<td>' + v2 + '</td>';
$('#product_totals tr').each(function(i3, v3){
var product_code = $('td', v3).eq(0).html();
if(product_code == v2) {
total_row_quantity += parseInt(model[i1].quantity);
total_row_value += parseFloat($('td', v3).eq(2).html()*model[i1].quantity);
$('td', v3).eq(1).html(total_row_quantity);
$('td', v3).eq(3).html(accounting.formatMoney(total_row_value, ''));
} else {
total_row_quantity = 0;
total_row_value = 0;
}
})
});
html += '</tr>';
});
$('#stock_order tbody').html(html);
}
The HTML is:
<tbody id="product_totals">
<tr data-id="1">
<td>JW1501</td>
<td class="code-quantity-total">0</td>
<td>79.00</td>
<td class="code-cost-total">0</td>
</tr>
<tr data-id="2">
<td>JW1502</td>
<td class="code-quantity-total">0</td>
<td>99.00</td>
<td class="code-cost-total">0</td>
</tr>
<tr data-id="3">
<td>JW1501-1</td>
<td class="code-quantity-total">0</td>
<td>20.00</td>
<td class="code-cost-total">0</td>
</tr>
<tr data-id="4">
<td>JW1502-2</td>
<td class="code-quantity-total">0</td>
<td>25.00</td>
<td class="code-cost-total">0</td>
</tr>
</tbody>
The list of rows (JW1501, JW1502) is dynamic.
The problem I am having is that if a variant of e.g. JW1502 is added, only the total quantity and value is calculated for that one. Any previous different variants of JW1502 are ignored.
How can I fix this?
Example content of var model:
[
{"product_id":"JW1501","size_id":"70A","colour_id":"小豹纹","quantity":"1"},
{"product_id":"JW1501","size_id":"75B","colour_id":"小豹纹","quantity":"2"},
{"product_id":"JW1502","size_id":"85A","colour_id":"黑色","quantity":"1"}
]
The above for JW1501 would show the incorrect quantity of 2, not 3.
...
$('#product_totals tr').each(function (i3, v3) {
console.log(v1, v2, v3)
...
Outputs:
Object {product_id: "JW1501", size_id: "70A", colour_id: "小豹纹", quantity: "2"}
"JW1501"
<tr data-id=​"1">​<td>​JW1501​</td>​<td class=​"code-quantity-total">​2​</td>​<td>​79.00​</td>​<td class=​"code-cost-total">​158.00​</td>​</tr>​
I have completely changed your printStock function to achieve your goal:
function printStock(model) {
$("#product_totals tr").each(function(){
var id = $("td:eq(0)", this).text().trim();
var price = parseFloat($("td:eq(2)", this).text());
var count = 0;
$.each(model, function(i, item){
if (item.product_id == id) count += (+item.quantity);
});
$("td:eq(1)", this).text(count);
$("td:eq(3)", this).text((count * price).toFixed(2));
});
var rows = $.map(model, function(item){
return [
"<td>" + item.product_id + "</td>",
"<td>" + item.size_id + "</td>",
"<td>" + item.colour_id + "</td>",
"<td>" + item.quantity + "</td>"
].join("");
});
var html = "<tr>" + rows.join("</tr><tr>") + "</tr>";
$('#stock_order tbody').html(html);
}
The main difference is that my code groups items in model by product_id for further counting.
Also refer my fiddle.

Categories

Resources