jQuery scope or race condition in AJAX/getJSON - javascript

I have a piece of jQuery code which invokes several getJSON() calls in quick succession:
var table = $("table#output");
for (var i in items) {
var thisItem = items[i];
$.getJSON("myService", { "itemID": thisItem }, function(json) {
var str = "<tr>";
str += "<td>" + thisItem + "</td>";
str += "<td>" + json.someMember + "</td>";
str += "</tr>";
table.append(str);
});
}
When I run this against a laggy server, the table gets populated with the expected json.someMember values (they arrive out of order: I don't mind that), but the thisItem column is populated with an unpredictable mixture of values from various iterations.
I'm assuming this is something to do with scope and timing - the callback function is reading thisItem from a wider scope? Am I right? How do I prevent this?
My current workaround is for the JSON service to return a copy of its inputs - which is unsatisfying to say the least.

Seems like a scoping issue due to the loop. Try this:
var table = $("table#output");
for (var i in items) {
var thisItem = items[i];
$.getJSON("myService", { "itemID": thisItem }, (function(thisItem) {
return function(json) {
var str = "<tr>";
str += "<td>" + thisItem + "</td>";
str += "<td>" + json.someMember + "</td>";
str += "</tr>";
table.append(str);
}
})(thisItem));
}
Edit: all I did was scope thisItem to the $.getJSON callback.

Javascript does not use block for scope. Scope is only based on functions.
If you want a new scope, you have to declare a new internal function and run it immediately, this is the only way to create a new scope in Javascript.
var table = $("table#output");
for( var i in items )
{
(function(){
var thisItem = items[i];
$.getJSON("myService", { "itemID": thisItem }, function(json)
{
var str = "<tr>";
str += "<td>" + thisItem + "</td>";
str += "<td>" + json.someMember + "</td>";
str += "</tr>";
table.append(str);
});
})();
}

Related

$.getJSON and for loop with an array and how to get them to work together - for loop completes before JSON is returned

I am trying to cycle through an array and with each value in the array, use $.getJSON to return some JSON and populate an HTML table with the return values.
I have been following this post, but seem not get this to work:
$.getJSON calls misbehaving inside a for loop
Here is my function:
$("#selectProviderTop").click(function() {
var restURL = window.location.protocol + "//" + window.location.hostname + (window.location.port == "" ? "" : (":" + window.location.port)) + "/restGetProvider/";
var selected = [];
var providerKey;
var providerID;
var providerLegacyID;
var providerName;
var finalURL;
var tr;
// First, create an array from the User Provider Keys...
var userProviderKeys = $("#hiddenUserProviderKeys").val();
selected = userProviderKeys.split(",");
console.log("selected: " + selected);
var tableHTML = "";
var focus = $("<div></div>"); // either match an existing element or create one: '<div />'
var arrayLength = selected.length;
for (var i = 0; i < arrayLength; i++) {
(function(i) {
console.log("i: " + i);
providerKey = selected[i];
console.log("providerKey: " + providerKey);
// Get that provider and populate the table...
finalURL = restURL + providerKey;
console.log("finalURL: " + finalURL);
focus.queue('apicalls', function(next) {
$.getJSON(finalURL, function(jsonObject) {
tableHTML += "<tr>";
tableHTML += "<td><a href=\"#\" onclick='selectProvider(\"" + providerKey + "\")'>" + jsonObject["providerName"] + "</a></td>";
tableHTML += "<td>" + jsonObject["providerID"] + "</td>";
tableHTML += "<td>" + jsonObject["providerLegacyID"] + "</td>";
tableHTML += "</tr>";
console.log("tableHTML: " + tableHTML);
next();
});
});
})(i);
}
// Replace table’s tbody html with tableHTML...
console.log("final tableHTML: " + tableHTML);
$("#tableProviderSelect tbody").html(tableHTML);
$('#modalSelectProviderForPTP').modal('show');
});
The userProviderKeys value is 0be32d8057924e718a8b6b4186254756,2dc5f826601e4cc5a9a3424caea4115f
The code never makes the $.getJSON call it just completes the for loop.
How do I update this code to get the first value in the array, grab the JSON, create the HTML, and then cycle through the loop?
I have tried setTimeout but that didn't help me out.
If you have some ideas, could you update my existing code - I understand better when I see the code itself. Thanks.
I don't know why you're doing this using queues. But you are, so I'm not going to rewrite your code to do it some other way.
The last few lines need to be called after all the queued functions have run, which means they should be called asynchronously. (Yes, you could make the whole thing synchronous as Marcus Höglund suggested, but that's no way to write scalable applications in javascript.) You could do this by adding another function to the queue containing these lines. Like this:
$("#selectProviderTop").click(function() {
var restURL = window.location.protocol + "//" + window.location.hostname + (window.location.port == "" ? "" : (":" + window.location.port)) + "/restGetProvider/";
var selected = [];
var providerKey;
var providerID;
var providerLegacyID;
var providerName;
var finalURL;
var tr;
// First, create an array from the User Provider Keys...
var userProviderKeys = $("#hiddenUserProviderKeys").val();
selected = userProviderKeys.split(",");
console.log("selected: " + selected);
var tableHTML = "";
var focus = $("<div></div>"); // either match an existing element or create one: '<div />'
var arrayLength = selected.length;
for (var i = 0; i < arrayLength; i++) {
(function(i) {
console.log("i: " + i);
providerKey = selected[i];
console.log("providerKey: " + providerKey);
// Get that provider and populate the table...
finalURL = restURL + providerKey;
console.log("finalURL: " + finalURL);
focus.queue('apicalls', function(next) {
$.getJSON(finalURL, function(jsonObject) {
tableHTML += "<tr>";
tableHTML += "<td><a href=\"#\" onclick='selectProvider(\"" + providerKey + "\")'>" + jsonObject["providerName"] + "</a></td>";
tableHTML += "<td>" + jsonObject["providerID"] + "</td>";
tableHTML += "<td>" + jsonObject["providerLegacyID"] + "</td>";
tableHTML += "</tr>";
console.log("tableHTML: " + tableHTML);
next();
});
});
})(i);
}
focus.queue('apicalls', function(next) {
// Replace table’s tbody html with tableHTML...
console.log("final tableHTML: " + tableHTML);
$("#tableProviderSelect tbody").html(tableHTML);
$('#modalSelectProviderForPTP').modal('show');
next();
});
});
Edit: Sunshine has pointed out that the linked stackoverflow post has mysterious references to the .dequeue method. In the accepted answer, this method is called explicitly after the tasks have been queued. I don't know whether this was necessary or not. I had thought that the problem was that the $.json bit wasn't happening until after the $("#tableProviderSelect tbody").html(tableHTML); part. But now I realise you wrote: "The code never makes the $.getJSON call it just completes the for loop." In that caseSunshine may have been right, and you need to add focus.dequeue('apicalls'); just after the last focus.queue(...);.

Display JSON data in tablular format of predefined format

I am working on a small project's interface. Basically, an API sends the following JSON data:
{
"wallet_transactions": [
{
"total_cost": "80.000",
"expense_type__name": "Gas",
"total_quantity": "5.000",
"trans_type": "Purchased"
},
{
"total_cost": "250.000",
"expense_type__name": "Gas",
"total_quantity": "35.000",
"trans_type": "Rent"
}
]}
The data basically shows how much of GAS was given, its cost and its means (on credit or it was bought).
I tried to build a table out of it directly, but it was dimmed unfriendly since GAS was written twice.
What I tried was:
$.each(response.wallet_transactions, function(index) {
var exp_name=response.wallet_transactions[index].expense_type__name;
var quantity=response.wallet_transactions[index].total_quantity;
var price=response.wallet_transactions[index].total_cost;
var trans_type=response.wallet_transactions[index].trans_type;
rows=rows+'<tr><td>' + exp_name + '</td>';
rows=rows + '<td>' + price + '</td>';
rows=rows + '<td>' + quantity + '</td>';
rows=rows + '</tr>';
});
The output that is needed now looks like the image below:
Group the data for each name together in another object, then build the table from that.
var table_data = {};
$.each(response.wallet_transactions, function(i, trans) {
var exp_name = trans.expense_type__name;
var quantity = trans.total_quantity;
var price = trans.total_cost;
var trans_type = trans.trans_type;
if (!table_data[exp_name]) {
table_data[exp_name] = {}
}
table_data[exp_name][trans_type] = {
quantity: quantity,
cost: price
};
}
$.each(table_data, function(name, data) {
rows += "<tr><td>" + name + "</td>";
rows += "<td>" + data.Rent.cost + "</td>";
rows += "<td>" + data.Rent.quantity + "</td>";
rows += "<td>" + data.Purchased.cost + "</td>";
rows += "<td>" + data.Purchased.quantity + "</td>";
rows += "</tr>";
}
Notice that $.each passes the array element as the second argument to the callback function, so you don't have to repeat response.wallet_transactions[index] on every line.

Trouble with creating table in Javascript

I have got the current code https://jsfiddle.net/rjw3f7yu/5/ that can plot table in HTML using Javascript code. However, there is this line that pops up stating "undefined" between row 1 and 2. Anyone knows what could be the problem here?
I am using bootstrap v3 just for extra info. Thanks!
HTML code:
<table class="table" id="wconclusiontable">
</table>
Javascript Code:
var counttopercentagec1event = [0, 1, 2, 3, 4, 5];
var counttopercentagec2event = [2, 33, 22, 32, 43, 52];
var counttopercentagec3event = [7, 17, 72, 37, 47, 51];
function wconclusiontable() {
var wtable = document.getElementById("wconclusiontable");
var row;
row += "<thead><tr><th>" + "Event #" + "</th>";
row += "<th>" + "Low" + "</th>";
row += "<th>" + "Medium" + "</th>";
row += "<th>" + "High" + "</th>";
row += "</tr></thead>";
for (var i = 2; i < 5; i++) {
row += "<tbody><tr><td>" + (i-1) + "</td>";
row += "<td>" + counttopercentagec1event[i-1] + "%" + "</td>";
row += "<td>" + counttopercentagec2event[i-1] + "%" + "</td>";
row += "<td>" + counttopercentagec3event[i-1] + "%" + "</td>";
row += "</tr></tbody>";
}
wtable.innerHTML = row;
}
wconclusiontable();
You have to initialise var row with empty string. Since row is undefined initially and you're adding directly with string, the initial row variable's value undefined is getting added. Hope that helps
Update fiddle - https://jsfiddle.net/rjw3f7yu/6/
var row = '';
It's probably because you never initialize your variable, "row". Initially, row is therefore undefined.
When you add to it the first time, you'll basically concatenate the strings "undefined" with "Event #".
Try defining row as:
var row = "";
Your updated code:
function wconclusiontable() {
var wtable = document.getElementById("wconclusiontable");
var row = "";
row += "<thead><tr><th>" + "Event #" + "</th>";
row += "<th>" + "Low" + "</th>";
row += "<th>" + "Medium" + "</th>";
row += "<th>" + "High" + "</th>";
row += "</tr></thead>";
for (var i = 2; i < 5; i++) {
row += "<tbody><tr><td>" + (i-1) + "</td>";
row += "<td>" + counttopercentagec1event[i-1] + "%" + "</td>";
row += "<td>" + counttopercentagec2event[i-1] + "%" + "</td>";
row += "<td>" + counttopercentagec3event[i-1] + "%" + "</td>";
row += "</tr></tbody>";
}
wtable.innerHTML = row;
}
Declaring a variable in a function in JavaScript generally serves one purpose - to set the scope of that variable, and avoid collision with a global variable. Since JavaScript is dynamically typed, JavaScript has no clue what it should be initializing your variable to at first. So, it chooses to define it as an "undefined" object.
During string concatenation, JavaScript will type coerce all objects involved in the operation to a string. This includes "undefined".
Why, then, does undefined appear after the header of the table? This happens because HTML renders the thead of the table before anything else. The best way to figure out what the potential issue is when dealing with a JavaScript issue like this is to open the inspector by right clicking the problematic element on the page and choosing "Inspect Element".
var row;
Replace above line by this
var row = new String("");
https://jsfiddle.net/wxaty4xs/

how json array iteration can be done?

my json array is like that which was received in html page how it can be display in a table ? means iteration .plz help? i am new .
[
{"studentId":"1001259101","firstName":"RAKESH","lastName":"DALAL","year":"2012","course":"BSC"},
{"studentId":"1001259101","firstName":"RAKESH","lastName":"DALAL","year":"2012","course":"BSC"},
{"studentId":"1001259101","firstName":"RAKESH","lastName":"DALAL","year":"2012","course":"BSC"}
]
Iterate over the array and display it in a table:
var jsonObject = [
{studentId: "1001259101", firstName: "RAKESH", lastName: "DALAL", year: "2012", course: "BSC"},
{studentId: "1001259101", firstName: "RAKESH", lastName: "DALAL", year: "2012", course: "BSC"},
{studentId: "1001259101", firstName: "RAKESH", lastName: "DALAL", year: "2012", course: "BSC"}
];
var output = "<table>";
for (var i = 0, len = jsonObject.length; i < len; i++) {
var line = jsonObject[i];
output += "<tr>";
output += "<td>" + line.studentId + "</td>";
output += "<td>" + line.firstName + "</td>";
output += "<td>" + line.lastName + "</td>";
output += "<td>" + line.year + "</td>";
output += "<td>" + line.course + "</td>";
output += "</tr>";
}
output += "</table>";
document.getElementById(...).innerHTML = output;
Well first of all JSON is (JavaScript Object Notation). Same JS, just a litlte bit different syntax for object notation.
You need to use AJAX in order to receive JSON data from other file, just have a look:
var xhr = new XMLHttpRequest();
var url = "myJSON.json"; // your JSON text file
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
var myResponse = JSON.parse(xhr.responseText);
display(myResponse); // send array to function
}
}
xhr.open("GET", url, true);
xhr.send(null);
function display(arr) {
var myTable = "<table>"; // create a table variable
for (var i = 0; i < arr.length; i++) { // loop through array
myTable += "<tr>";
myTable += "<td>" + arr[i].studentId + "</td>";
myTable += "<td>" + arr[i].firstName + "</td>";
myTable += "<td>" + arr[i].lastName + "</td>";
myTable += "<td>" + arr[i].year + "</td>";
myTable += "<td>" + arr[i].course + "</td>";
myTable += "</tr>";
}
myTable += "</table>";
document.getElementById("myAwesomeTable").innerHTML = myTable; // display the final result in to html
}
Use AJAX in order to open your JSON text file, it could be .txt, .json and etc.
Use JSON.parse() to convert your JSON text to Array
Send that Array to function
Create a table and hold everything in variable like a text
Loop through array
Display your table in to html

Why is nan being added to data?

I make an ajax call to a servlet which sends JSON data. I parse it with JSON.parse() and put it into a string to display in table tag in html. But with every row cell data it is showing NaN. I have checked the data there is no NaN.
Here's the code.
var dataFromJSON = JSON.parse(result);
var count = dataFromJSON.count;
var str = "<table id='customers'><tr><th>LOGGED DATE</th></tr>";
for (var i = 0; i < count; i++) {
str += "<tr><td>" + dataFromJSON.records[i].common.logged_date +
+"</td>";
str += "</tr>";
}
str += "</table>";
$("#data").html(str);
The data in dataFromJSON.records[i].common.logged_date is 2016-02-23 10:11:43, but the table shows 2016-02-23 10:11:43NaN.
Please help.
Here:
str += "<tr><td>" + dataFromJSON.records[i].common.logged_date+
+ "</td>";
You have two + right after another. The browser tries to interpret this whitespace as a number
Remove one of the +.
You have put ++ please use only +
var dataFromJSON = JSON.parse(result);
var count = dataFromJSON.count;
var str = "<table id='customers'><tr><th>LOGGED DATE</th></tr>";
for (var i = 0; i < count; i++) {
str += "<tr><td>" + dataFromJSON.records[i].common.logged_date +"</td>"; //chnage here
str += "</tr>";
}
str += "</table>";
$("#data").html(str);

Categories