How to wait for $.getJSON()? - javascript

I have this problem where I do an .each() on this group of selects, for each one it fires off a call to the server for some data to populate it with. However I couldn't figure out why it would only populate the bottomest one. Then I threw in some alerts() and realized it was only running the call back function on the last one multiple times. I realized that by the time the first JSON call was done, $(this) was something different... How can I get it to wait so all of them will be populated by the proper call?
HERE IS THE SCRIPT PART:
var thisbundle;
var testcount = 0;
//get bundle options first..
$("select.bundle").each(function() {
thisbundle = $(this);
testcount++;
var url = "/order/getpricing/" + thisbundle.attr("id");
//clear it out...
//thisbundle.children().remove();
var passbundle = thisbundle;
$.getJSON(url, function(data, passbundle) {
var options = '';
for (n = 0; n < data.length; n++) {
options += '<option value="' + data[n].volumeID + '">' + explainPricing(data, n) + '</option>';
}
passbundle.html(options);
});
});
AND HERE IS THE FORM PART:
<div id="bundles">
<table>
<%foreach (KODmvc.Models.Product prod in Model.products)
{%>
<%if (prod.NumberOfCourses > 1)
{ %>
<tr><td><img src="<%=prod.Icon %>" /></td><td><b><%=prod.Title%></b><br /><%=prod.Description%></td><td><select class="bundle" id="<%=prod.ProductID %>"><option value="-1">None</option>"</select></td></tr>
<%} %>
<%} %>
</table>
</div>

Enclose the ajax call in an anonymous function like this. This creates a new closure for every select element. Each of these closures will remember it's own value for passbundle.
$("select.bundle").each(function(){
thisbundle = $(this);
testcount++;
var url = "/order/getpricing/" + thisbundle.val();
alert(thisbundle.id);
//clear it out...
//thisbundle.children().remove();
(function(){
var passbundle = thisbundle;
$.getJSON(url, function(data, passbundle){
var options = '';
for(n = 0; n < data.length; n++){
options += '<option value="' + data[n].volumeID + '">' + explainPricing(data, n) + '</option>';
}
passbundle.html(options);
});
})();
});

Declare thisbundle in your function and not in the global scope:
$("select.bundle").each(function(){
var thisbundle = $(this);
// …
});
Otherwise the global object would be overwritten with each iteration that the callback function would then use.

You could also use async : false, although that might be the wrong direction to head if you are looping. But it is worth looking at.

Related

Accessing data in Google Maps PHP with jQuery

I'm having trouble correctly accessing data in Google Maps API php.
Heres what example data looks in php:
{"destination_addresses":["Destination address"],"origin_addresses":["Origin address"],"rows":[{"elements":[{"distance":{"text":"3.3 km","value":3314},"duration":{"text":"6 mins","value":334},"status":"OK"}]}],"status":"OK"}
Heres my .js:
$(function(){
$("#button").click(function(){
var start = $("#start").val(); //gets start and end values from input fields, and passes them to .php which is not visible here.
var end = $("#end").val();
$.ajax({url: "googlemaps.php",
method: "GET",
data : { "start" : start, "end" : end },
success: function(result) {
print(result);
},
error: function(xhr){
alert("Error: " + xhr.status + " " + xhr.statusText);
}
});
});
});
function print(result){
var length = "";
for(var i = 0;i < result.rows.length;i++){
length += result.rows[i].text+ "<br/>";
$("#div").html(length);
}}
It should calculate the distance between two addresses, and it currently returns unidentified (which is ok), since
length += result.rows[i].text+ "<br/>";
is not correct. I have no idea how to access value "text":"3.3 km", or it's equivalent in my code. I know it is an object inside "distance", which is an array item of "elements", which is an array item of "rows".
Its structured like:
rows[0].elements[0].distance.text
You might not need the loop, but if you were to use it you would do something like.
for (var i = 0;i < result.rows.length; i++) {
for (var k = 0;k < result.rows[i].elements.length; k++) {
length += result.rows[i].elements[k].distance.text + "<br/>";
}
}
$("#div").html(length);
If your array is result, then you can access the distance by using this:
var distance = result['rows'][0]['elements'][0]['distance']['text'];

jQuery $.get() within for loop scope [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 7 years ago.
I'm new to working with AJAX, but I've been researching it for the past two hours to help in my scenario. I haven't made any progress. :(
Regardless, my issue is that the subPages array is out of scope when I'm outside $.get(...). I've tried using when() and done() for my code, but just can't get it right still.
I think the problem lies within the iterations going through a for loop since I have pages[i] in multiple sections of my code being used. That's why I can't use when() and done() when needed.
Here's what I have:
var subPages = [];
var containsSub = '/sites/Pages/';
var tempString = '';
// iterate through the pages array in reverse
for(var i = pages.length - 1; i >= 0; i--){
// grab all <a> within response text
var getLinks = $.get(baseURL + pages[i]).then(function(responseData){
var $response = $(responseData);
var $links = $response.find('a');
// push each valid link into subPages array
$links.each(function(index, $link){
if(this.href.indexOf(containsSub) > -1){
subPages.push(this.href);
}
});
// subPages array is loaded with the correct values
console.log("subPages inside get: " + subPages);
});
// empty here
console.log("subPages outstide all: " + subPages);
Edit: With the addition of the then chain and code, I'm having an undefined for subPages[i]
var subPages = [];
var containsSub = '/sites/Pages/';
var tempString = '';
// iterate through the pages array in reverse
for(var i = pages.length - 1; i >= 0; i--){
// grab all <a> within response text
var getLinks = $.get(baseURL + pages[i]).then(function(responseData){
var $response = $(responseData);
var $links = $response.find('a');
// push each valid link into subPages array
$links.each(function(index, $link){
if(this.href.indexOf(containsSub) > -1){
subPages.push(this.href);
//console.log("<a href='"+ this.href + "'>" + this.href + "</a>" + " <br>");
}
});
console.log("subPages inside get: " + subPages);
})
.then(function(){
console.log("subPages outstide all: " + subPages);
// print bold for current main page
tempString += "<strong><a href='"+ baseURL + pages[i] + "'>" + pages[i].substr(27,pages[i].length) + "</a><strong>" + " <br>";
for(var i = 0; i < subPages.length - 1; i++){
console.log("<a href='"+ subPages[i] + "'>" + subPages[i] + "</a>" + " <br>");
}
subPages = [];
pages.splice(i, 1);
})
}
11/25 Edit: I fixed the issue below with my answer by removing some complications and decided that an AJAX request was more in logic.
var subPages = [];
var containsSub = '/sites/it/InfoProtect/Pages/';
var tempString = '';
// iterate through the pages array in reverse
for(var i = pages.length - 1; i >= 0; i--){
// grab all <a> within response text
var getLinks = $.ajax({
url: baseURL + pages[i],
async: false,
success: function(responseData){
var $response = $(responseData);
var $links = $response.find('a');
// push each valid link into subPages array
$links.each(function(index, $link){
if(this.href.indexOf(containsSub) > -1){
subPages.push(this.href);
}
});
}
})
Your for loop immediately executes all iterations of the loop. The subPages array is populated after the last line of console.log has run.
$.get is asynchronous, so after calling it, the code inside .then is not immediately called. So, it continues to the next iteration of your loop, finally exits, and shows an empty subpages array, because your data hasn't returned yet.
Here's a quick idea of how to wait for your ajax calls, prior to logging the array (untested):
var ajaxCalls = [];
// iterate through the pages array in reverse
for(var i = pages.length - 1; i >= 0; i--){
// grab all <a> within response text
var getLinks = $.get(baseURL + pages[i]).then(function(responseData){
var $response = $(responseData);
var $links = $response.find('a');
// push each valid link into subPages array
$links.each(function(index, $link){
if(this.href.indexOf(containsSub) > -1){
subPages.push(this.href);
}
});
// subPages array is loaded with the correct values
console.log("subPages inside get: " + subPages);
});
ajaxCalls.push(getLinks);
}
$.when.apply(null, ajaxCalls).then(function() {
// not empty here
console.log("subPages outstide all: " + subPages);
});
issue is that the subPages array is out of scope when I'm outside
$.get(...)
$.get() returns an asynchronous response . Try chaining .then() to $.get() to maintain same scope as initial .then()
var getLinks = $.get(baseURL + pages[i]).then(function(responseData){
})
.then(function() {
console.log("subPages outstide all: " + subPages);
})
Try creating an IIFE within for loop to pass i
e.g.,
var pages = ["a", "b", "c"];
for(var i = pages.length -1; i >= 0; i--) {
(function(j) {
var dfd = $.Deferred(function(d) {
setTimeout(function() {
d.resolve(j)
}, Math.random() * 1000)
}).promise()
.then(function(n) {
console.log("first", n, pages[n]);
return n
}).then(function(res) {
console.log("second", res, pages[res])
})
}(i))
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

How to generate dynamic strings in javascript closure call

I'm creating a long list of select items and wanted to create a closure call to add each option, then queue the call (via a setTimer) so the browser would not hang.
My implementation works great, but here is what has me puzzled, the following code:
var mainList = $('#mainList');
for (var i=0;i < 100000; i++) {
var self = mainList, addOption = function() {
$(self).append('<option value=' + i + '>' + i + '</option>');
};
$.queue.add(addOption, this);
}
generates:
<option value='100000'>100000</option>
<option value='100000'>100000</option>
<option value='100000'>100000</option> etc...
Where I would like to have it generate the options:
<option value='1'>1</option>
<option value='2'>2</option>
<option value='3'>3</option> etc...
I'm struggling with how the closure is executed, it makes sense that the addOption() method is called when i == 100000, but I would like the call to reflect the value of i at the time it is queued up.
Is there an easy trick I am missing here?
Set addOption by IIFE which returns a function
var mainList = $('#mainList');
for (var i = 0; i < 100000; i++) {
var self = mainList,
addOption = (function(i) {
return function() {
$(self).append('<option value=' + i + '>' + i + '</option>');
}
})(i);
$.queue.add(addOption, this);
}
Prior to the introduction of Function.prototype.bind, it was certainly necessary to employ a closure specifically to trap one or more vars in situations like this.
Now, that approach is considered to be a bit clumsy.
You can define a basic addOption function, then spawn versions of it in the loop, using addOption.bind() to bind in both mainList as "thisArg", and i as the first formal variable :
var mainList = $('#mainList');
var addOption = function(i) {
this.append('<option value=' + i + '>' + i + '</option>');
};
for (var i=0; i<100000; i++) {
$.queue.add(addOption.bind(mainList, i), this);
}

JSON Object (how to not load the entire object on DOM initially)

So i am reading a local json file that consist of {[Object,Object,Object.....]}
I am using the
$.getJSON('products.json', function (pdata) {
for (var i = 0; i < pdata.data.length; i++) {
AppendtoDom(pdata.data[i]);
}
The above code reads the json objects and appends to the DOM, but i want to initially load only 100 objects at a time and on scroll keep appending.
Say there are around 1200 objects. How do i go about this?
My implementaion so far
$(function(){
loadData();
});
function loadData(){
$.getJSON('products.json', function (pdata) {
var i = 0;
function addtoDom(num){
var limit = Math.min(i + num, pdata.data.length);
for(; i < limit; i++){
getInformation(pdata.data[i]);
}
}
addtoDom(100);
$('.content').jscroll({
callback: addtoDom(100)
});
});
}
function getInformation(obj){
var content = "";
for (var i = 0; i < 4; i++) {
content += '<li>';
content += "<img src='" + obj.imageUrl + "' style='width:200px;height:200px'/>";
content += '<div class="productName">' + obj.fullName + "</div>";
content += '<div class="price">Price: ' + obj.price + "</div>";
content += '</li>';
}
$("<ul class= 'view'>" + content + "</ul>").appendTo('.content');
}
Similar question i asked in How would i implement an infinite scroll in my DOM
You can put all the objects you get back from the Ajax call into a persistent variable, add the first 100 to the DOM, keep a counter of how many you've added so far and then upon scrolling to a certain point, add another 100, add another 100 and so on.
$.getJSON('products.json', function (pdata) {
var i = 0;
function addMore(num) {
var limit = Math.min(i + num, pdata.data.length);
for (; i < limit; i++) {
AppendtoDom(pdata.data[i]);
}
}
// add the first 100
addMore(100);
// then set up whatever scroll detection you want here and
// when you decide that it has scrolled enough to add some more
// you just call addMore(100) again
});
In your specific implementation of the above idea, you have an implementation mistake. You have to pass a function reference for the callback so change this:
$('.content').jscroll({
callback: addtoDom(100)
});
to this:
$('.content').jscroll({
callback: function() {addtoDom(100);}
});
Assign your JSON to a variable and dynamically render them as needed.
var json;
$.getJSON('products.json', function (pdata) {
JSON = pdata;
};
// Scheduling logic
AppendtoDom(json[i]);

Asynchronous Problems with Javascript Hacker News API

Hacker News recently released an API that I am using to display what the current top ten items are on Hacker News. I am running into some problems.
When I run the code below, the order of the items on the frontpage are inaccurate, jumping from the second one in the frontpage to the fourth, to the first, to the fifth, to the third and so on. Running the code again results in a slightly different order again.
$.getJSON('https://hacker-news.firebaseio.com/v0/topstories.json', function(json) {
var convoText = '<ol>';
for (var i = 0; i < 10; i++) {
(function(i) {
$.getJSON('https://hacker-news.firebaseio.com/v0/item/' + json[i] + '.json', function(json2) {
convoText += '<li>' + json2.title + '</li>';
if (i === 9) {
convoText += '</ol>';
addConvo(convoText);
}
});
})(i);
}
});
I understand that this is an effect of Javascript's asynchronous nature. How can I fix it?
The knack is to create and append a <li><a></a></li> structure synchronously in the loop - thereby establishing the correct order - then populate it asynchronously with json2 data when it arrives.
$.getJSON('https://hacker-news.firebaseio.com/v0/topstories.json', function(json) {
var $ol = $('<ol/>').appendTo(...);//wherever
for (var i = 0; i < Math.min(json.length, 10); i++) {
(function(i) {
var $a = $('<li><a></a></li>').appendTo($ol).find('a');
$.getJSON('https://hacker-news.firebaseio.com/v0/item/' + json[i] + '.json', function(json2) {
$a.attr('href', json2.url).text(json2.title);
});
})(i);
}
});
You will have to complete the .appendTo(...) line. I don't know from the question where the <ol>...</ol> is appended.
You can use jQueries $.when for that:
$.getJSON('https://hacker-news.firebaseio.com/v0/topstories.json', function(json) {
var requests = [];
for (var i = 0; i < 10; i++) {
requests.push($.getJSON('https://hacker-news.firebaseio.com/v0/item/' + json[i] + '.json'));
}
$.when.apply($, requests).done(function() {
var results = [].slice.call(arguments);
var list = results.map(function(arr) {
return '<li>' + arr[0].title + '</li>';
});
var convoText = '<ol>' + list.join('') + '</ol>';
console.log(convoText);
});
});
There are a few ways to fix this. The easiest is, instead of appending to convoText, use an array, and set its index when you get data. Like data[i] = json2;. Then when all your data is fetched, join your array.
A more structural fix would be to rearchitect your loop as a collection of promises, and construct your HTML when they have all resolved (what #xat was alluding to above).

Categories