Nested 'for' loop not triggered until AFTER first 'for' loop - javascript

I'm relatively new to javascript/jquery.
I am trying to run a nested for loop which dynamically creates HTML content which I then append into a table in my body. The first 'for' loop pulls data from Parse, and creates an html table row script, which I then append to a table in html body. The nested 'for' loop should run for each item in the first loop, and create a table row element directly below.
However, for some reason (and I see this when I debug), the first for loop completes before triggering the nested loop. Is there any obvious reason or syntax that is causing this?
Thanks in advance!
$(document).ready(function(){
var currentUser = Parse.User.current();
var htmlContent = "";
if (currentUser) {
console.log(currentUser.get("full_name"));
$("#user").html(currentUser.get("full_name"));
var QrUrl = Parse.Object.extend("qr_url");
var qr_query = new Parse.Query(QrUrl);
qr_query.equalTo("createdBy", currentUser);
qr_query.include("createdBy");
qr_query.ascending("createdAt")
qr_query.find({
success: function(qrid_results) {
for (var i = 0; i < qrid_results.length; i++) {
var qridentry = qrid_results[i];
htmlContent="<tr><td>"+qridentry.get("title")+"</td><td>"+"Created"+"</td><td>"+qridentry.createdAt+"</td><td>"+qridentry.get("createdBy").get("full_name")+"</td></tr>";
$('#trackingtable').append(htmlContent);
var QrLogger = Parse.Object.extend("qr_logger");
var qrlog_query = new Parse.Query(QrLogger);
qrlog_query.equalTo("qrid", qridentry);
qrlog_query.include("createdBy");
qrlog_query.include("qrid");
qrlog_query.find({
success: function(qrlog_results) {
for (var j = 0; j < qrlog_results.length; j++) {
var qrlogentry = qrlog_results[j];
try{
var user_id = obj.get("createdBy").get("full_name");
console.log(user_id);
}
catch(err){
user_id="Unknown Scanner";
}
var dated = qrlogentry.updatedAt;
htmlContent="<tr><td>"+qrlogentry.get("qrid").get("title")+"</td><td>"+"Scanned"+"</td><td>"+dated+"</td><td>"+user_id+"</td></tr>";
$('#trackingtable').append(htmlContent);
}
}
});
//$('#trackingtable').dataTable({ });
}
}
});
}
});

Here's a way to make things work with Promises such that they run in the order they would if find were synchronous:
qr_query.find().then(function(qrid_results) {
var promise = Parse.Promise.as();
for (var i = 0; i < qrid_results.length; i++) {
promise = promise.then(function() {
// Build qrlog_query...
return qrlog_query.find();
}).then(function(qrlog_results) {
for (var j = 0; j < qrlog_results.length; j++) {
// Do whatever synchronous work you want...
}
});
}
return promise;
});
```
For more info, Google for [javascript promises].

Your call to the find method surrounding your second for loop is actually deferring execution until whenever that second find completes. If you want the inner for loop to execute synchronously with the first for loop you'll need to structure your code so that either 1. Each deferred execution is chained to start in your desired order. 2. You move all the HTML edits into a separate function that only executes after all qrlog queries have been succefully executed, storing the intermediate results somewhere until you're ready to render your HTML.

Related

AngularJS - Is controller code executed line by line?

I'm new to AngularJS, and is experimenting AngularJS with Twitch API.
I have a list of channels that I'm interested in, defined as var channels.
Then I use the $http.get function to loop through another array, twitchList.channels, which contains the API addresses that I'm supposed to call.
(function() {
var app = angular.module('twitchList', []);
app.controller('twitchController', ['$http', function($http){
var twitchList = this;
twitchList.channels = [];
var channels = ["freecodecamp", "storbeck", "terakilobyte", "habathcx","RobotCaleb","thomasballinger","noobs2ninjas","beohoff", "MedryBW"];
for (var i = 0; i < channels.length; i++ ) {
twitchList.channels.push({
name: channels[i],
api: 'https://api.twitch.tv/kraken/streams/' + channels[i],
})
}
var data_list = [];
for (var j = 0; j < twitchList.channels.length; j++) {
$http.get(twitchList.channels[j].api).success(function(data){
data_list.push(data);
})
}
// Issue arises here!
console.log(data_list);
console.log(data_list.length);
}]);
})();
The API calls seems to be working perfectly, however, I need to get the results of the API call into an array, called data_list. Now, when I print data_list, and data_list.length, what happens is that data_list.length always returns 0, and data_list is sometimes populated (meaning it's either 0 size array or 9 size array). Even though the property of the array has a length 9, but calling .length always gives 0.
This let me think that the controller code is not executed line by line? Or is there something wrong with my logic?
Can someone give me a pointer? Thanks
No, this line:
data_list.push(data);
will be executed when you receive a response on the http request sent a line above. Hence the following lines:
console.log(data_list);
console.log(data_list.length);
will output [] and 0
I've not used it before, but could you possibly use $q.all in order to resolve multiple promises? I've used the equivalent $.when function in jQuery to achieve this in the past.
var data_list = [];
var promise_array = [];
var request;
for (var j = 0; j < twitchList.channels.length; j++) {
request = $http.get(twitchList.channels[j].api);
request.success(function(data) {
data_list.push(data);
});
promise_array.push(request);
}
$q.all(promise_array).then( function() {
console.log(data_list);
console.log(data_list.length);
});

Inner function does not assign value to element dynamically created by outer function in jQuery

I am trying to create a search result dynamically added below the form through ajax call using parameter given in the form. I can add results using a table, but in these results I have client ids where I need Name, so I again use ajax call for each client id which return success but can not assign the values to corresponding table field. Below is my code, what is wrong here?
var stopMulti = 0;
$('#salesreport-end_date').change(function(){
var endDate = $(this).val();
var startDate = $('#salesreport-start_date').val();
var param = startDate+'#'+endDate+'#date';
if(stopMulti == 0){
$.get('index.php?r=reports/sales-report/sales-report',{ id : param }, function(data){
var sReport = JSON.parse(data);
if(typeof sReport != 'undefined'){
if(sReport.length != 0){
$('<div id="sales-report-div-1" class="col-sm-12"><h2 id ="sales-report-heading-1">Sales Report</h2></div>').insertAfter('#sub-button-div');
$('<table class="table-bordered text-center col-sm-12"><tr id="sales-report-row-head"><th class="text-center">Invoice Number</th><th class="text-center">Client Name</th><th class="text-center">Company Name</th><th class="text-center">Phone</th><th class="text-center">Net Total</th><th class="text-center">VAT</th><th class="text-center">Total</th></tr></table>').insertAfter('#sales-report-heading-1');
$('<br><br>').insertAfter('#sales-report-div-1');
var count = 0;
for(var i = 0; i < sReport.length; i++){
$('<tr><td id=sales-report-col-1'+count+'></td><td id=sales-report-col-2'+count+'></td><td id=sales-report-col-3'+count+'></td><td id=sales-report-col-4'+count+'></td><td id=sales-report-col-5'+count+'><td id=sales-report-col-6'+count+'></td><td id=sales-report-col-7'+count+'></td></tr></div>').insertAfter('#sales-report-row-head');
$('#sales-report-col-1'+count).html(sReport[i].invoice_id);
$.get('index.php?r=reports/sales-report/get-client',{ id : sReport[i].client_id}, function(client){
var client = JSON.parse(client);
$('#sales-report-col-2'+count).text(client.client_name);
$('#sales-report-col-3'+count).text(client.company_name);
$('#sales-report-col-4'+count).text(client.telephon);
});
$('#sales-report-col-5'+count).html(sReport[i].sub_total);
$('#sales-report-col-6'+count).html(sReport[i].taxrate);
$('#sales-report-col-7'+count).html(sReport[i].invoice_total);
count++;
}
}
}
});
}
stopMulti = 1;
});
That is I am getting blank fields for client name, company name and telephone.
Firebug does not find any error in the code.
The $.get callback function will be called asynchronously, and therefor any value like count will be changed before the .get returns.
A simple technique to overcome this is to use anonymous function, so your function should look like this:
for(var i = 0; i < sReport.length; i++)
{
f = function(i,count)
{
... rest of your code
}
f(i,count);
count++;
}
Here is a general example: https://jsfiddle.net/BentalSW/auyLq7rp/
You can't use count inside the inner AJAX Callback because by the time those asynchronous calls complete count will equal sReport.length and the wrong divs will be updated.
Use a .forEach loop instead of the for loop:
sReport.forEach(function(report, count) {
...
});
This will give you a value of count that is correctly bound to the current iteration count.
NB: it shouldn't be necessary to call JSON.parse on data that's already in JSON format - jQuery will do that for you automatically.

HTML rendering before for-loop finishes executing, only first item in for-loop showing

I am trying to render an html page that contains all of the posts that a user has received. Right now the issue I am having (shown under Way 1) is that when I call the function renderPosts after the web socket is received, only the first post in the array is rendered (the array has more than one post in it).
On the other hand, Way 2 in which I have no for loop and instead manually render each post works in that all four posts are rendered. But I need to be able to work with an arbitrary number of posts which is why I need to use the for loop.
I am using socket.io and javascript.
Way 1:
socket.on('postsToRender', function(arrayOfPostsToRender) {
renderPosts(arrayOfPostsToRender);
});
function renderPosts(arrayOfPostsToRender) {
for (var index = 0; index < arrayOfPostsToRender.length; index++) {
renderPost(arrayOfPostsToRender[index]);
}
}
function renderPost(postToRender) {
var feed = document.getElementById("feed");
var postContent = document.createTextNode(postToRender.content);
var post = document.createElement("div");
post.appendChild(postContent);
feed.appendChild(post);
}
Way 2:
socket.on('postsToRender', function(arrayOfPostsToRender) {
renderPost(arrayOfPostsToRender[0]);
renderPost(arrayOfPostsToRender[1]);
renderPost(arrayOfPostsToRender[2]);
renderPost(arrayOfPostsToRender[3]);
});
function renderPost(postToRender) {
var feed = document.getElementById("feed");
var postContent = document.createTextNode(postToRender.content);
var post = document.createElement("div");
post.appendChild(postContent);
feed.appendChild(post);
}
Try this:
function renderPosts(arrayOfPostsToRender) {
for (var index = 0; index < arrayOfPostsToRender.length; index++) {
(function(i){
renderPost(arrayOfPostsToRender[i]);
})(index);
}
}

Arguments in Parse.com query.find success callback

Thanks for the help in advance.
I'm working on an practice assigment using Phonegap and Javascript. Long story short: I need to use Parse.com to store information about some Lego minifigures. The problem I'm having right now is due mostly to my inexperience in Javascript.
I'm working on letting the user add tags to the figures. The user enters them, separated by comma, and I then split the string. That's working OK.
Now, I need to add the tags that don't exist yet to my database. For this, I search for any tags with that description (using query.find) and then, if it exists, I don't create it, I just modify the relationship. If it doesn't exist, I create it and then modify the relationship.
My problem is: I can't seem to be able to access the tag description (the string) from within the success callback of query.find. I'm pretty sure it's because of the scope. Is there any proper way to access variables from withing a success callback, besides the results array?
My current code is as follows:
var Figure = Parse.Object.extend("Figure");
var Tag = Parse.Object.extend("Tag");
var nombre = $('#nombre').val();
var serie = $('#serie').val();
var figure = new Figure({"Name":nombre,"Series":serie});
var tags = $('#tags').val();
res = tags.split(","); //split the
figure.save().then(function() {
for (var i = 0; i < res.length; i++) { //for each tag
var query = new Parse.Query(Tag); //create the query.
query.equalTo("Description", res[i]);
query.find( {//execute query
success: function(results, res[i]) {
if (results.length > 0){ //if there are results.
var tag = results[0]; //get the tag
var relation_tag = tag.relation("figures"); //get the relation
relation_tag.add(figure); //add figure to relation
tag.save();
}
else { //if there are no results, the tag does not exist.
new_tag = new Tag({"Description":res[i]});
//ABOVE THIS LINE: res[i] is always undefined.
var relation_tag = new_tag.relation("figures"); //get the relation
relation_tag.add(figure); //add the figure
new_tag.save();
}
},
//error with query
error: function() {
alert("ERROR");
}
});
}
}, function(error) {
alert("No se pudo guardar la figura");
});
In the success callback, res[i] always is undefined, I assume that it's because of the scope.
This is a very common problem in async Javascript programming. You are doing something like this:
for (var i = 0; i < array.length; i++) {
anAsyncFunction(function(result) { // inner function
doSomethingWith(array[i]);
}
}
The problem is that in Javascript functions store outer variables by reference and not by value, which means that a function looks up the value of a variable from an outer scope, when it is executed and not when it is defined. Since the code is async the the inner function is called after the for loop completed and at this point we have i === array.length, so array[i] === array[array.length] === undefined.
To avoid this you can use an immediately invoked function expression (IIFE, pronounced "iffy"):
for (var i = 0; i < array.length; i++) {
anAsyncFunction((function(j) { // IIFE
return function innerFunction(result) { // inner function
doSomethingWith(array[j]); // j instead of i
}
})(i); // passing "value of i"
}
Because the IIFE is invoked immediately, the current value is of i is passed and stored into j and when the inner function executes it uses the correct value.
So in your case this should work:
success: (function(j) { // IIFE
return function(results) {
if (results.length > 0) {
var tag = results[0];
var relation_tag = tag.relation("figures");
relation_tag.add(figure);
tag.save();
}
else { //if there are no results, the tag does not exist.
new_tag = new Tag({"Description":res[j]}); // j instead of i
var relation_tag = new_tag.relation("figures");
relation_tag.add(figure);
new_tag.save();
}
}
})(i) // pass "value of i"
If you prefer, you can also pass the description itself instead of just the index to the IIFE (I think I would do it that way):
success: (function(description) { // IIFE
return function(results) {
if (results.length > 0) {
var tag = results[0];
var relation_tag = tag.relation("figures");
relation_tag.add(figure);
tag.save();
}
else { //if there are no results, the tag does not exist.
new_tag = new Tag({"Description":description}); // description
var relation_tag = new_tag.relation("figures");
relation_tag.add(figure);
new_tag.save();
}
}
})(res[i]) // pass description
var Tag = Parse.Object.extend("Tag");
var query = new Parse.Query(Tag);

How to loop through me/friends and assign each user it's picture from {USERID}/picture from FB.api

Using javascript I get list of facebook friends though it only returns name and id now, but I need to get the picture of each user. I try to loop through the response and then try to call the api to get picture, but due to it's async call I can't associate the returned picture with the index of the friend in the array. *this is kinda a problem that I've had with asynchronous programming in general, is there a standard pattern for this?
Example.
FB.api('me/friends', function(response) {
if(response.error == null){
var friendsSale = response.data;
var len = friendsSale.length;
for(var x=0; x<len; x++){
FB.api(friendsSale[x].id+'/picture', function(response) {
//x no longer is the same x as the initial call, and I can't pass in the orignal array object into the FB.api function to return as part of the response... or can I?
friendsSale[x].pictureUrl = response;
});
}
}
//Then how do I know when I have all the pictures set so I can then set datamodle with the complete friend array?
m.friends(friendsSale);
}
});
Yes, there is a pattern for this: a Closure
...
var len = friendsSale.length;
for (var i = 0; i < len; i++) {
(function() {
var j = i;
FB.api(friendsSale[i].id+'/picture', function(response) {
friendsSale[j].pictureUrl = response;
});
})();
}
To know when all all calls have returned you can simply keep a counter of returned calls, e.g.
...
var len = friendsSale.length;
var returnedCallsCounter = 0;
for (var i = 0; i < len; i++) {
(function() {
var j = i;
FB.api(friendsSale[i].id+'/picture', function(response) {
friendsSale[j].pictureUrl = response;
// Track number of returned calls
returnedCallsCounter++;
// Check if all calls have returned
if (returnedCallsCounter == len) {
m.friends(friendsSale);
}
});
})();
}
Simple solution for you :
All you have to do is query this :
https://graph.facebook.com/user_id/picture
and you will get the users profile picture. For example :
Querying https://graph.facebook.com/4/picture (with no access token BTW - try it in chrome pron incognito mode) :
<img src="https://graph.facebook.com/4/picture">
will yeild this smiling face :
Now you know Marks fbid :P

Categories

Resources