qarthandso qarthandso - 3 months ago 11
HTML Question

JavaScript Variable Stuck inside Loop - Can't Access

I'm doing an AJAX call to retrieve all stores in a certain state. The problem is, is that the last 2 lines can't access any of the variable content that I built up in the return function from the

get
call.

var states = ['TX', 'AZ];

for(j = 0; j < states.length; j++) {
var fullHTML = '';
var full2;
$.get("//127.0.0.1:8000/api/state-stores/", {state: states[j]}, function(data) {
console.log(data.stores.length);

for(var i = 0; i < data.stores.length; i++) {
store_html = '<tr><td>' + data.stores[i].name + '</td><td>'
fullHTML += store_html;
}
})
// PROBLEM HERE
console.log(fullHTML); //empty
$("#" + states[j].toLowerCase() + "-table").html(fullHTML); //does nothing
}


What can I do to fix this? I've been trying weird global variable tricks and all sorts of hack-y things but nothing's working.

UPDATE
I also tried placing my last two lines inside the
$.get
call but
states[j]
is for some reason inaccessible inside the loop.
After debugging, it seems like just
j
from the outer loop is inaccessible inside this return function. Is there a way to fix that?




UPDATE 2

I followed the advice given in the answer and I am getting properly concatenated strings for
fullHTML
, but, each
fullHTML
string is replacing only the last state in the table. It's as if the
TX
tag is the only one receiving all the updates. I think this is closure-based but I don't know how to resolve this.

$(function() {
var states = ['AZ', 'CA', 'CO', 'GA', 'NV', 'NC', 'SC', 'TX'];
// var states = ['CA'];
for(var j = 0; j < states.length; j++) {
var current = j;
$.get("//127.0.0.1:8000/api/state-stores/", {state: states[j]}, function(data) {
var fullHTML = '';
for(var i = 0; i < data.stores.length; i++) {
store_html = '<tr><td>' + data.stores[i].name + '</td><td>' + data.stores[i].address + '<span class="small-table"><br>' + data.stores[i].city + '</span></td><td>' + data.stores[i].city + '</td><td>' + data.stores[i].state + '</td><td>' + data.stores[i].postal_code + '</td></tr>'
fullHTML += store_html;
}
$('#' + states[current].toLowerCase() + '-table').html(fullHTML);
})
}

});

Answer

That is an asynchronous $.get function. That is to say, code execution will not wait at that line until the $.get is done, perform the callback function, and then continue onto the next line after the $.get. In reality, it will perform an asynchronous call to the $.get function, and continue execution immediately to the console.log(fullHTML) line. There is no guarantee at this point that the $.get function has finished or even started, as it's being executed asynchronously.

As for your issue with the accessibility to j, that's a problem that comes back to closures. Your j variable, when accessed inside of the $.get callback, will reference the j variable that has been looped through already, and will return the incorrect j value, as you probably want the value of j from when the $.get request was executed. So, you'll want to store j inside of the local closure by declaring it explicitly like var current = j, the instead of referencing j inside of your $.get callback, you'd reference current, and it would perform as intended. This doesn't work!

EDIT

We can force create a new scope if we declare an anonymous function and execute the $.get inside of that context. It's not a pretty solution, but it works!

To fix your code, put your calls inside of the $.get callback like so:

var states = ['TX', 'AZ'];

for(j = 0; j < states.length; j++) {
    var fullHTML = '';
    var full2;
    (function() {
        var current = j;
        $.get("//127.0.0.1:8000/api/state-stores/", {state: states[j]}, function(data) {
            console.log(data.stores.length);

            for(var i = 0; i < data.stores.length; i++) {
                store_html = '<tr><td>' + data.stores[i].name + '</td><td>'
                fullHTML += store_html;
            }
            console.log(fullHTML); // not empty anymore
            $("#" + states[current].toLowerCase() + "-table").html(fullHTML); //does stuff
        });
   })();
}

Now, your code will only perform the lines that were broken before AFTER it has populated the fullHTML variable.