-1

I am having a difficult time understanding the scope of some vars in javascript. in the below code - I want to make an ajax call, and loop over my results creating some html elements that get populated a it later...also in that original loop I want to populate an array that I'll use later.

when i fist wrote it I created the array in the .done function, but I got an undefined when testing it for .length ( things_ids.length ) later. So i moved it up to the calling function, but it's still undefined when i go to use it later.

...
var things_ids = [];

$( '.filter' ).click( function() {

    $.ajax({
        method: 'POST',
        url: 'list.html',
        data: { ... },
        dataType: 'json',
        cache: false
    })
   .fail( function( jqXHR ) {

        $( '#msg' ).append( '...fail msg...' + jqXHR.responseText + ' ' + jqXHR.statusText );

    })
    .done( function ( data ) {

        // html back to user
        var divs = '';

        if ( data['DATA'].length ) {

            for ( var i = 0; i < data['DATA'].length; i++ ) {

                divs += '<div class="header" >' + data['DATA'][i][1] + ' <span class="toggle-thing" >( show )</span></div>\
                <div class="container" id="thing_' + data['DATA'][i][0] + '" ></div>';
                things_ids.push( data['DATA'][i][0] );

            }

        } else {

            divs = '<div>We have no data.</div>';

        }

        // populate the select
        $( '#things_container' ).html( divs ).slideDown( 3000 ).fadeIn( 1000 );

    });


    // here's where it goes wonky for me

    alert( 'thing id : ' + things_ids ); // this show nothing

    // here i want to load data to the newly created divs
    if ( things_ids.length ) {

        for ( var t = 0; t < things_ids.length; t++ ) {

            alert( 'thing id : ' + things_ids[g] );

            $( '#thing_' + things_ids[t] ).hide(); // start by hiding the div - toggle later....

            // spinner
            $( '#thing_' + things_ids[t] ).html( '<img src="spinner.gif" alt="data loading" />' );
                // content

            $( '#thing_' + things_ids[t] ).load( '/things', function( response, status, xhr ) {

                if ( status == 'error' ) {

                    var msg = 'Sorry but there was an error loading the thing: ';
                    $( '#thing_' + things_ids[t] ).html( msg + xhr.status + ' ' + xhr.statusText );

                }

            });

        }

    }

});
j-p
  • 3,698
  • 9
  • 50
  • 93
  • could you be more specifically? If you want to see what is in `things_ids` unse the console, `console.log(things_ids);` – vasilenicusor Jul 07 '15 at 05:14
  • Think `async`. You're kicking off your AJAX call then immediately going on to the following code. The AJAX call may take tens of milliseconds to return, while your code is busy looking for data that hasn't yet arrived. You need to wrap the code you want to execute in a callback function to be executed when the AJAX call returns. You've already done some of this - just not quite enough. –  Jul 07 '15 at 05:16
  • possible duplicate of [How to return the response from an asynchronous call?](http://stackoverflow.com/questions/14220321/how-to-return-the-response-from-an-asynchronous-call) –  Jul 07 '15 at 05:17
  • @vasilenicusor - yes that is it - but it is undefined...and that is my question (hopefully i clarified above) – j-p Jul 07 '15 at 05:19
  • @HoboSapiens - hopefully there is an example of how to do what you suggest in the link to the previous answer.. I'll check and get back to you. OK, since i already have a ".done" function call, I guess I can move the code there? – j-p Jul 07 '15 at 05:20
  • Thx hobo - moving the code inside the .done callback function did the trick, for some reason, I thought the code i had to populate the .html() of the divs would not be done, so i put my code outside that function, just not thinking i guess.. – j-p Jul 07 '15 at 05:30

2 Answers2

1

Javascript is asynchronous. In your case you are trying to check the length after the ajax call. But the call is not yet returned the value. What you can do is put your if condition in a function and in the done callback make a call to this function and pass the array to it.

    $( '.filter' ).click( function() {

        $.ajax({
            method: 'POST',
            url: 'list.html',
            data: { ... },
            dataType: 'json',
            cache: false
        })
       .fail( function( jqXHR ) {

            $( '#msg' ).append( '...fail msg...' + jqXHR.responseText + ' ' + jqXHR.statusText );

        })
        .done( function ( data ) {

            // html back to user
            var divs = '';
            var things_ids = [];

            if ( data['DATA'].length ) {

                for ( var i = 0; i < data['DATA'].length; i++ ) {

                    divs += '<div class="header" >' + data['DATA'][i][1] + ' <span class="toggle-thing" >( show )</span></div>\
                    <div class="container" id="thing_' + data['DATA'][i][0] + '" ></div>';
                    things_ids.push( data['DATA'][i][0] );

                }

            } else {

                divs = '<div>We have no data.</div>';

            }


            // populate the select
            $( '#things_container' ).html( divs ).slideDown( 3000 ).fadeIn( 1000 );
            makeACallToThis( things_ids );

        });



    });


function makeACallToThis( things_ids ){
    // here's where it goes wonky for me

    alert( 'thing id : ' + things_ids ); // this show nothing

    // here i want to load data to the newly created divs
    if ( things_ids.length ) {

        for ( var t = 0; t < things_ids.length; t++ ) {

            alert( 'thing id : ' + things_ids[g] );

            $( '#thing_' + things_ids[t] ).hide(); // start by hiding the div - toggle later....

            // spinner
            $( '#thing_' + things_ids[t] ).html( '<img src="spinner.gif" alt="data loading" />' );
                // content

            $( '#thing_' + things_ids[t] ).load( '/things', function( response, status, xhr ) {

                if ( status == 'error' ) {

                    var msg = 'Sorry but there was an error loading the thing: ';
                    $( '#thing_' + things_ids[t] ).html( msg + xhr.status + ' ' + xhr.statusText );

                }

            });

        }

    }

}
Swaprks
  • 1,553
  • 11
  • 16
  • 'JavaScript is asynchronous.' - Are you absolutely _sure_ about the way you've worded that statement? – Oka Jul 07 '15 at 05:25
  • `makeACallToThis( things_ids )` should be called after `$( '#things_container' ).html( divs ).slideDown( 3000 ).fadeIn( 1000 );` – vasilenicusor Jul 07 '15 at 05:39
-1

add async: false in the definition of ajax. This will hold the execution of rest of code till you get response of ajax. (NOT recommended)

$.ajax({
    method: 'POST',
    url: 'list.html',
    data: { ... },
    dataType: 'json',
    cache: false,
    async: false
})

if you don't want to make asynchronous ajax, add the code after alert( 'thing id : ' + things_ids ); // this show nothing at the end of done function of ajax –

EDIT

Without asynchronous, because it could freeze the browser for long request, you can use this

...
var things_ids = [];

$( '.filter' ).click( function() {

    $.ajax({
        method: 'POST',
        url: 'list.html',
        data: { ... },
        dataType: 'json',
        cache: false
    })
   .fail( function( jqXHR ) {

        $( '#msg' ).append( '...fail msg...' + jqXHR.responseText + ' ' + jqXHR.statusText );

    })
    .done( function ( data ) {

        // html back to user
        var divs = '';

        if ( data['DATA'].length ) {

            for ( var i = 0; i < data['DATA'].length; i++ ) {

                divs += '<div class="header" >' + data['DATA'][i][1] + ' <span class="toggle-thing" >( show )</span></div>\
                <div class="container" id="thing_' + data['DATA'][i][0] + '" ></div>';
                things_ids.push( data['DATA'][i][0] );

            }

        } else {

            divs = '<div>We have no data.</div>';

        }

        // populate the select
        $( '#things_container' ).html( divs ).slideDown( 3000 ).fadeIn( 1000 );

        // here's where it goes wonky for me

        alert( 'thing id : ' + things_ids ); // this show nothing

        // here i want to load data to the newly created divs
        if ( things_ids.length ) {

            for ( var t = 0; t < things_ids.length; t++ ) {

                alert( 'thing id : ' + things_ids[g] );

                $( '#thing_' + things_ids[t] ).hide(); // start by hiding the div - toggle later....

                // spinner
                $( '#thing_' + things_ids[t] ).html( '<img src="spinner.gif" alt="data loading" />' );
                    // content

                $( '#thing_' + things_ids[t] ).load( '/things', function( response, status, xhr ) {

                    if ( status == 'error' ) {

                        var msg = 'Sorry but there was an error loading the thing: ';
                        $( '#thing_' + things_ids[t] ).html( msg + xhr.status + ' ' + xhr.statusText );

                    }

                });

            }

        }

    });

}); 
vasilenicusor
  • 2,023
  • 1
  • 21
  • 37
  • 1
    No. No. No. Setting `async:false` will block execution in the browser and freeze everything until the AJAX call returns. This is categorically __not__ the way to do things. The `A` in AJAX stands for _asynchronous_! –  Jul 07 '15 at 05:24
  • 1
    @HoboSapiens i know that, this is why I add comment on the question, but i see that you down voted so the comment is on answer (EDIT) – vasilenicusor Jul 07 '15 at 05:31