I am kinda new to Ajax/Json so I would like to know if the following is at least near to the best practice. The goal is to update specific columns (in this case quantity and price) of a table every x seconds.
I got a HTML table defined like this:
<table id="#edition_table>
<tbody>
<tr>
<td class="name">Lightning Bolt</td>
[...]
<td class="qty">2</td>
<td class="price">$4.99</td>
<tr>
<tr>
<td class="name">Fireball</td>
[...]
<td class="qty">0</td>
<td class="price">$0.07</td>
<tr>
[...]
</tbody>
</table>
And a JS function defined like this:
function edition_update(edition)
{
var table_rows = $('#edition_table').find('tbody tr td.name a');
$.ajax({
type: 'GET', url: 'ajax_edition_update.php', data: { edition : edition }, dataType: 'json',
success: function(json_rows)
{
var new_qty, new_price;
table_rows.each(function(index) {
var td_id = $(this).attr('href').replace('?card=', '');
for (i in json_rows) {
if (json_rows[i].card_id == td_id)
{
new_qty = json_rows[i].qty;
new_price = json_rows[i].low_price;
break;
}
}
var parent_tr = $(this).parent().parent();
parent_tr.find('td.qty').text(new_qty);
parent_tr.find('td.price').text(!isNaN(new_price) ? '$' + new_price : new_price);
});
}
});
setTimeout(edition_update, 30000, edition);
}
The PHP file returns a JSON including card_id, qty and low_price.
This does work fine. I guess I could set up a data-id=[card_id] on the class=name td to get rid of the .replace, but that kinda blows up the html footprint as the id is already present.
The real question is whether any performance improvements (especially regarding the two loops) are possible or necessary? The target number of rows per table is arround 500 and the content and order is totally dynamic/unpredictable, of course.
Related
I have a dynamically generated CSV file from another vendor that I am puling in and need to show in a table on my site. The problem is I need to be able to manipulate the data from the CSV so it can show the corrected values in the html table. In the end I need the HTML table to just display the Products, not the Mixed Sets.
I am using jquery and the papaparse library to get the data and parse it in a table in html. My codepen is here:
https://codepen.io/BIGREDBOOTS/pen/YQojww
The javascript pulls the initial csv values and display in a table, but I can't figure out how to to add together the values. If there is a better way of going about this, like converting the CSV to some other form of data like JSON, That is fine too.
My CSV looks like this:
product_title,product_sku,net_quantity
Product 1,PRD1,10
Product 2,PRD2,20
Product 3,PRD3,30
Mixed Set 1,MIX1,100
Mixed Set 2,MIX2,50
Mixed Set 3,MIX3,75
The Javascript I am using is:
function arrayToTable(tableData) {
var table = $('<table></table>');
$(tableData).each(function (i, rowData) {
var row = $('<tr class="rownum-' + [i] + '"></tr>');
$(rowData).each(function (j, cellData) {
row.append($('<td class="' + [i] + '">'+cellData+'</td>'));
});
table.append(row);
});
return table;
}
$.ajax({
type: "GET",
url: "https://cdn.shopify.com/s/files/1/0453/8489/t/26/assets/sample.csv",
success: function (data) {
$('body').append(arrayToTable(Papa.parse(data).data));
}
});
My rules for the mixed set:
Mixed Set 1 should add 100 to Product 1 and Product 2.
Mixed Set 2 should add 50 to Product 2 and Product 3.
Mixed Set 3 should add 75 to Product 1, Product 2 and Product 3.
I'd like to end up with Just the products output, and the correct numbers added to the formula.
The end result would be a table with Product 1 = 185, Product 2 = 245, and Product 3 = 155.
While it would be even better if the top THEAD elements were in a "th", It's fine if that is too complicated.
<table>
<tbody>
<tr class="rownum-0">
<td class="0">product_title</td>
<td class="0">product_sku</td>
<td class="0">net_quantity</td>
</tr>
<tr class="rownum-1">
<td class="1">Product 1</td>
<td class="1">PRD1</td>
<td class="1">185</td>
</tr>
<tr class="rownum-2">
<td class="2">Product 2</td>
<td class="2">PRD2</td>
<td class="2">245</td>
</tr>
<tr class="rownum-3">
<td class="3">Product 3</td>
<td class="3">PRD3</td>
<td class="3">155</td>
</tr>
</tbody>
</table>
Without knowing the size of the dataset you're working with, I suggest you first iterate through all the CSV dataset in order to populate a list of products with the correct values, and then iterate again on that to populate your HTML table:
function datasetToMap(data) {
var ret = {};
//Initialize a map with all the product rows
$(data).each(function(index, row) {
if(row[0].startsWith("Product")) {
ret[row[1]] = row; //Using the SKU as the key to the map
}
});
//Apply your mixed sets rules to the elements in the ret array
$(data).each(function(index, row) {
if(row[1] === "MIX1") {
ret["PRD1"][2] += 100;
ret["PRD2"][2] += 100;
}
//Do the same for Mixed sets 2 and 3
});
return ret;
}
function appendMapToTable(map) {
var $table = $('#my-table');
Object.keys(map).forEach(function(key, i) {
var rowData = map[key];
var row = $('<tr class="rownum-' + [i] + '"></tr>');
$(rowData).each(function (j, cellData) {
row.append($('<td class="' + [j] + '">'+cellData+'</td>'));
});
$table.append(row);
});
}
$.ajax({
type: "GET",
url: "https://cdn.shopify.com/s/files/1/0453/8489/t/26/assets/sample.csv",
success: function (data) {
appendMapToTable(datasetToMap(Papa.parse(data).data));
}
});
Note that this expects a table with id my-table to be already present in your HTML: you could manually parse the first row of your CSV data to add the table headings.
Also note that if your CSV dataset is very big this is definitely not an optimal solution, since it requires iterating through all its lines twice and then iterating again through all the list built with computed values.
I would like to know how I can do order data from API pre-date very time.
How its should be:
<table>
<tr>
<th>2017-02-17</th>
</tr>
<tr>
<td>some date</td>
<td>some date</td>
<td>some date</td>
</tr>
<tr>
<th>2017-02-15</th>
</tr>
<tr>
<td>some date</td>
<td>some date</td>
<td>some date</td>
</tr>
</table>
example:
URL API: http://api.tradingeconomics.com/calendar?c=guest:guest
My code:
$.ajax({
url: "url",
type: "Get",
datatype: "JSON",
contentType: "application/json",
error : function (data) { console.log("error:" + data) },
success: function (response) {
response.forEach(function (data) {
$('.top_table').append(
"<tr>" +
"<th>DATE</th>" +
"</tr>" +
"<tr id='content'>" +
"<td>some text....</td>" +
"<td>some text....</td>" +
"<td>some text....</td>" +
"<td>some text....</td>" +
"</tr>"
);
});
console.log(response);
}
});
But its print like:
date
result
date
result
How I can do it?
I don't know if I've understood your question very well, but I think that could give you some tips.
Your API returns an Array of Objects if you make and Ajax GET request, so you can order this Array before inserting the content into the DOM:
The next example order the data in descending order comparing the Date parameter, there is another date parameter called LastUpdate, I don't know if you want to use it in your logic.
$.get("https://api.tradingeconomics.com/calendar?c=guest:guest", function (data) {
//---Order the array received from the server
data.sort(function (a, b) {
return (new Date(a.Date)) - (new Date(b.Date));
});
//---The data is ordered, you can insert it into the DOM
});
In the other hand, your code inserts a row with the header content and a row with the body content in each iteration, this is not correct. You need to add all the header texts in a single row and each item of the Array needs to be inserted in separated rows.
Here you have a working example to give you an idea of the process:
https://jsfiddle.net/elchininet/ym8qp415/
EDIT: Seeing your comments, I understand now that you want to grouping the data not just ordering it. I recommend you to use the reduce method of the Array class to create a new data separated by dates and after that you can insert the data in the table:
var regdate = /^(\d{4}\-\d{2}\-\d{2})T(\d{2}:\d{2}:\d{2})$/;
//---Sort the data from the server
data.sort(function (a, b) {
return (new Date(a.Date)) - (new Date(b.Date));
});
var group = data.reduce(function (before, current) {
var day = current.Date.replace(regdate, "$1");
var hour = current.Date.replace(regdate, "$2");
if (!before[day]) {
before[day] = [];
}
current.Hour = hour;
before[day].push(current);
return before;
}, {});
//---The data is ordered and grouped, you can insert it into the DOM
Working example with fake data (Because of the example in the API returns only one day):
http://jsfiddle.net/elchininet/nvv4fnon/
Without a plugin, this is going to be difficult to achieve unless the "calendar" information is pulled from a database, it may be worth sorting in PHP, loading and displaying the data in jQuery
<script type="javascript">
$(function() {
$("BODY").on("click", "TH[data-orderby]", function() {
var order = $(this).data("orderby") || "date";
var parent = $(this).parents("table").parent();
$.ajax({
url: 'calendar?c=guest:guest',
data: 'order-by=' + URLEncode(order),
type: 'POST',
success: function(response) {
parent.html(response);
},
error: function(event, request, settings) {
console.warn("Ajax Error", request);
}
})
});
});
</script>
<div class="parent">
<table>
<thead>
<tr>
<th>Heading</th>
<th data-orderby="date">Date</th>
<th>Title</th>
<th data-orderby="last_updated">Last Updated</th>
</tr>
</thead>
<tbody>
<tr>
<td>Data</td>
<td>Data</td>
<td>Data</td>
<td>Data</td>
</tr>
</tbody>
</table>
Because we use $("BODY").on("click", "TH[data-orderby]"), even when the data is reloaded from the server, the sort functionality will remain.
I hope this helps.
Im trying to remove rows from a table that match 2 values ie using and AND operator to match where user = username and phrase = searchedphrase
So for example:
<table>
<tr class="user1">
<td class="user">user1</td>
<td class="phrase">phrase1</td>
<td class="filename">filename1</td>
<td><button class="blacklist" data-value="user1" data-phrase="phrase1">Blacklist</button></td></tr>
<tr class="user1">
<td class="user">user1</td>
<td class="phrase">phrase2</td>
<td class="filename">filename2</td>
<td><button class="blacklist" data-value="user1" data-phrase="phrase2">Blacklist</button></td></tr>
<tr class="user1">
<td class="user">user1</td>
<td class="phrase">phrase1</td>
<td class="filename">filename3</td>
<td><button class="blacklist" data-value="user1" data-phrase="phrase1">Blacklist</button></td></tr>
I've used data- in the button elements to pass values to an ajax script that executes some php, i suspect this may not be an optimal way of doing things im very new to jquery.
I can identify multiple rows and manipulate them using
$('.'+user).css("background-color","red");
I guess i need a function to pull out the rows matching both values and iterate them over .remove?
This is how the script is triggered, any pointers on the best way to do this would be greatly appreciated!
<script type="text/javascript">
$('.blacklist').click(function()
{
var user = $(this).data('value');
var phrase = $(this).data('phrase');
if(user != '')
{
$.ajax({
url: 'test.php',
method: 'POST',
dataType:'json',
data: {'user' : user}, //then getting $_POST['user']
success: function(data)
{
}
If you wanted to do this all within your jquery function, it'd look something like:
$('.blacklist').click(function()
{
var user = $(this).data('value');
var phrase = $(this).data('phrase');
if (user != '')
{
//loop through each row of the table
$('table tr').each(function()
{
//check if criteria matches
if ($(this).find('.user').html() == user && $(this).find('.phrase').html() == phrase)
{
//remove the row
$(this).remove();
//if you needed to use ajax to perform some kind of DB operation
//you could perform this here as well
/*
$.ajax({
url: 'test.php',
method: 'POST',
dataType:'json',
data: {'user' : user}, //then getting $_POST['user']
success: function(data)
{
}
});
*/
}
});
}
});
You can use filter() to create element collections that aren't easily accomplished using selectors.
Assume you want to run some conditional checks on the text in your td.user and td.phrase classes you can do something like.
var userMatch= /* some value */
var phraseMatch = /* some other value */
$('tr.user').filter(function(){
var user= $(this).find('.user').text(), phrase=$(this).find('.phrase').text();
/* return boolean based on conditions desired*/
return user === userMatch && phrase === phraseMatch;
/* remove the new collection returned by "filter" */
}).remove();
filter() API Docs
I've just thought of a way to do this with php - I can create an md5 hash of the user and phrase then assign it to a data-hash value. This would enable me to use something like
var hash = $(this).data('hash');
$('.'+hash).remove();
I have a table list of companies with a [+] button next to each company name in my table list.
When user clicks [+], a javascript function uses jquery ajax to get and append a new table row below the row clicked, which will then display an indented list of departments.
All works great.. until we get to our beloved IE. I'm using IE 8, not tried this on prev versions.
Table list item HTML before a click:
<tr id="row1">
<td align="center">
<div id="button1" class="on" onclick="javascript:expandDepartments(1)"></div>
</td>
<td>Company 1</td>
</tr>
The onClick function:
<script>
function expandDepartments(s_cid) {
if ($('#button'+s_cid+'').hasClass('on')) {
$('#button'+s_cid+'').removeClass('on').addClass('off');
if ( document.getElementsByName('rowafter'+s_cid+'').length == 0) { //if the department list does not exist for this company (first time getting departments)
$.ajax({
type: 'POST',
url: 'ajax/common.php',
dataType: 'html',
data: 'a=getHomePageDepartments&cid='+s_cid+'',
success: function(txt){
setTimeout(function(){
$('#homeCompaniesList tbody').find('#row'+s_cid+'').after(txt);
},1000);
}
});
}else{ //otherwise, just re-show the row again, no need to request it again
setTimeout(function(){
var x = document.getElementsByName('rowafter'+s_cid+'');
for(var k=0;k<x.length;k++)
x[k].style.display = '';
},1000);
}
} else if ( $('#button'+s_cid+'').hasClass('off') ) { //hide the row when MINUS image clicked
$('#button'+s_cid+'').removeClass('off').addClass('on');
var x = document.getElementsByName('rowafter'+s_cid+'');
alert(x.length);
for(var k=0;k<x.length;k++)
x[k].style.display = 'none';
}
}
</script>
The HTML output for a company containing multiple departments:
<tr style="display:;" name="rowafter1"><*td data not important*..
<tr style="display:;" name="rowafter1">
<tr style="display:;" name="rowafter1">
<tr style="display:;" name="rowafter1">
<tr style="display:;" name="rowafter1">
Now, look at javascript function, line:
alert(x.length);
In Firefox, it alerts 5
In IE it alerts 0
Which tells me, the HTML elements injected into the page using jquery ajax are not accessible in IE and I have no idea why. Do I need to set an ajax parameter for ie?? Not sure.. please assist.
ta
IE has an issue with getElementsByName
Alternatively, why not use jQuery?
var x = $('*[name="rowafter'+s_cid+'"]'); //get all elements with name rowafterN
getElementsByName() does not work in < IE9. If you are using jQuery, use the attribute selector:
var x = $('[name="rowafter' + s_cid + '"]');
alert(x.length);
I've been struggling with this issue for a while now. Maybe you can help.
I have a table with a checkbox at the beginning of each row. I defined a function which reloads the table at regular intervals. It uses jQuery's load() function on a JSP which generates the new table.
The problem is that I need to preserve the checkbox values until the user makes up his mind on which items to select. Currently, their values are lost between updates.
The current code I use that tries to fix it is:
refreshId = setInterval(function()
{
var allTicks = new Array();
$('#myTable input:checked').each(function() {
allTicks.push($(this).attr('id'));
});
$('#myTable').load('/get-table.jsp', null,
function (responseText,textStatus, req ){
$('#my-table').tablesorter();
//alert(allTicks + ' length ' + allTicks.length);
for (i = 0 ; i < allTicks.length; i++ )
$("#my-table input#" + allTicks[i]).attr('checked', true);
});
}, $refreshInterval);
The id of each checkbox is the same as the table entry next to it.
My idea was to store all the checked checkboxes' ids into an array before the update and to change their values after the update is done, as most of the entries will be preserved, and the ones that are new won't really matter.
'#myTable' is the div in which the table is loaded and '#my-table' is the id of the table which is generated. The checkbox inputs are generated along with the new table and with the same ids as before.
The weird thing is that applying tablesorter to the newly generated table works, but getting the elements with the stored ids doesn't.
Any solutions?
P.S: I know that this approach to table generation isn't really the best, but my JS skills were limited back then. I'd like to keep this solution for now and fix the problem.
EDIT:
Applied the syntax suggested by Didier G. and added some extra test blocks that check the status before and after the checkbox ticking.
Looks like this now:
refreshId = setInterval(function()
{
var allTicks = []
var $myTable = $('#my-table');
allTicks = $myTable.find('input:checked').map(function() { return this.id; });
$('#myTable').load('/get-table.jsp', null,
function (responseText,textStatus, req ){
$myTable = $('#my-table');
$('#my-table').tablesorter();
var msg = 'Before: \n';
$myTable.find('input').each(function(){
msg = msg + this.id + " " + $(this).prop('checked') + '\n';
});
//alert(msg);
//alert(allTicks + ' length ' + allTicks.length);
for (i = 0 ; i < allTicks.length; i++ ){
$myTable.find('#' + allTicks[i]).prop('checked', true);
}
msg = 'After: '
$myTable.find('input').each(function(){
msg = msg + this.id + " " + $(this).prop('checked') + '\n';
});
//alert(msg);
});
}, $refreshInterval);
If I uncomment the alert lines, and check 2 checkboxes, on the next update I get (for 3 row table):
Before: host2 false
host3 false
host4 false
object [Object] length 2
After: host2 false
host3 false
host4 false
Also did a previous check on the contents of the array and it has all the correct entries.
Can the DOM change or working with an entirely new table instance be a cause of this?
EDIT2:
Here's a sample of the table generated by the JSP (edited for confidentiality purposes):
<table id="my-table" class="tablesorter">
<thead>
<tr>
<th>Full Name</th>
<th>IP Address</th>
<th>Role</th>
<th>Job Slots</th>
<th>Status</th>
<th>Management</th>
</tr>
</thead>
<tbody>
<tr>
<td>head</td>
<td>10.20.1.14</td>
<td>H</td>
<td>4</td>
<td>ON</td>
<td>Permanent</td>
</tr>
<tr>
<td>
<input type="checkbox" id="host2" name="host2"/>
host2
</td>
<td>10.20.1.7</td>
<td>C</td>
<td>4</td>
<td>BSTART</td>
<td>Dynamic</td>
</tr>
<tr>
<td><input type="checkbox" id="host3" name="host3"/>
host3</td>
<td>10.20.1.9</td>
<td>C</td>
<td>4</td>
<td>BSTART</td>
<td>Dynamic</td>
</tr>
<tr>
<td><input type="checkbox" id="host4" name="host4"/>
host4</td>
<td>10.20.1.11</td>
<td>C</td>
<td>4</td>
<td>BSTART</td>
<td>Dynamic</td>
</tr>
</tbody>
</table>
Note that the id and name of the checkbox coincide with the host name. Also note that the first td does not have a checkbox. That's the expected behavior.
Changing 'special' attributes like disbaled or checked should be done like this:
$(...).attr('checked','checked');
or this way if you are using jQuery 1.6 or later:
$(...).prop('checked', true); // more reliable
See jQUery doc about .attr() and .prop()
Here's your piece of code modified with a few optimizations (check the comments):
refreshId = setInterval(function()
{
var allTicks = [],
$myTable = $('#myTable'); // select once and re-use
// .map() returns an array which is what you are after
// also never do this: $(this).attr('id').
// 'id' is a property available in javascript and
// in .map() (and in .each()), 'this' is the current DOMElement so simply do:
// this.id
allTicks = $myTable.find('input:checked').map(function() { return this.id; });
$myTable.load('/get-table.jsp', null, function (responseText,textStatus, req ) {
$myTable.tablesorter();
//alert(allTicks + ' length ' + allTicks.length);
for (i = 0 ; i < allTicks.length; i++ )
// avoid prefixing with tagname if you have the ID: input#theId
// #xxx is unique and jquery will use javascript getElementById which is super fast ;-)
$myTable.find('#' + allTicks[i]).prop('checked', true);
});
}, $refreshInterval);
Let us assume that the JavaScript does retrieve and set the checkboxes ticks.
Then there still is a problem with the asynchrone Ajax call.
First try it with a very large $refreshInterval.
Place the for-loop before the tablesorter call.
Do not setInterval, but setTimeout and schedule this for one single time.
Then in the load function schedule the next time.
This prevents overlapping calls which were a possible cause for the error.
But may stop refreshing, when the load is not called. (Not so important.)
After lots of painful hours of digging up every small detail, I realized that my problem was not how I coded the thing, nor was it stuff like unexpected DOM changes, but a simple detail I failed to see:
The id I was trying to assign to the checkbox contained a period (".") character.
This causes lots of problems for jQuery when trying to look up that sort of id, because a period as-is acts as a class descriptor. To avoid this, the period character must be escaped using 2 backslashes.
For example:
$("#my.id") // incorrect
$("#my\\.id") // correct
So then the fix in my case would be:
$myTable.find('#' + allTicks[i].replace(".", "\\.")).prop('checked', true);
... and it finally works.
Thanks everyone for all your helping hands!