1

I've tried searching for this issue and have found a number of questions from people having problems with their for loops and it only returning the last element. Try as I might, I'm just not understanding what I'm doing wrong here.

I'm really new to Javascript so I think I'm having an issue with closures? What I'm writing is supposed to grab some data from the JSON returned from the Urbandictionary API. The JSON fetch works but my for loop to build divs with that data is not working properly.

function word(term, def, example)
    {
      this.term = term;
      this.def = def;
      this.example = example;
     }

var lexicon = ['highway+salute', 'score', 'ya+heard+me', 'utz'];

var color = ['reddy', 'bluey', 'greeny', 'tanny', 'darky'];

for (var i = 0; i < lexicon.length; i++) {

$.getJSON('http://api.urbandictionary.com/v0/define?term=' + lexicon[i],
    function(data){
        lillyLivered = [];
        lillyLivered.push(new word(data.list[0].word, data.list[0].definition, data.list[0].example));
        console.log(lillyLivered);

        $('.container-fluid').html('<div class="row message unread">' + 
        ' <div class="col-xs-12 col-sm-6 col-lg-3 malok ' + color[i] + ' id="">' +
        '<div class="row dict">' + 
        '<div class="col-xs-9 col-sm-9 col-lg-9">' + 
        '<h3 class="term term0">' +
        lillyLivered[i].term + 
        '</div><div class="col-xs-3 col-sm-3 col-lg-3">' +
        '<div class="col-xs-3 col-sm-3 col-lg-3">' + 
        ' <span class="step size-32"><i class="icon ion-ios7-world"></i></span>' +
        '</div></div>' + 
        '<div class="row dict"><div class="col-xs-12 col-sm-12 col-lg-12">' +
        '<p class="definition definition0">' + 
        lillyLivered[i].def +
        '</p><p class="eg eg0">' + 
        lillyLivered[i].example + 
        '</p></div></div></div>'
        );
    }
)};

I have a fiddle up here: http://jsfiddle.net/orenthal/5X96B/

This code works somewhat if I use lillyLivered[0]. It doesn't load at all if I use lillyLivered[i]. I get the following error:

Uncaught TypeError: Cannot read property 'term' of undefined (anonymous function)

I'm getting confused by it. Console output of lillyLivered only shows the item at the index position 2 in the lillyLivered array. In this case "ya heard me":

lillyLivered
[ word
    def: "Do you understand me? [ya heard] popular in New Orleans. the phrase is suppose to come after a sentence."
    example: "Say brah, I'm chillin here in Orlando, ya heard me. ↵-C-murder's innocent, ya heard me."
    term: "ya heard me"

I thought that maybe the problem existed because of the calls to lillyLivered[i].term etc... were contained within the same for loop as the calls to the JSON data? So I tried doing this with two loops.

I created a new for loop that would take care of creating the divs and outputting the items from the lillyLivered array but I ran into the same issue here.

Uncaught TypeError: Cannot read property 'term' of undefined 

But this has helped with one of the issues, that being that the console output of lillyLivered now lists all four words from lexicon along with their definitions and examples.

lillyLivered
[word, word, word, word]

I have a fiddle of this one up here: http://jsfiddle.net/orenthal/5bTrJ/

I get the feeling that I'm being dense and missing something very obvious here.

Jeremy
  • 394
  • 4
  • 13

3 Answers3

2

Yes your issue has to do with closures. Your get requests are likely finishing (and calling their respective callbacks) long after all of the requests have been sent. Because of this, by the time the callbacks are called, i will be equal to lexicon.length. You can fix this by enclosing the get request in its own functional scope and passing the index i at that moment into that scope. Like this:

for (var i = 0; i < lexicon.length; i++) {
    (function(i) {
        $.getJSON('http://api.urbandictionary.com/v0/define?term=' + lexicon[i],
            function(data){
                lillyLivered = [];
                lillyLivered.push(new word(data.list[0].word, data.list[0].definition, data.list[0].example));
                console.log(lillyLivered);

                $('.container-fluid').html('<div class="row message unread">' + 
                ' <div class="col-xs-12 col-sm-6 col-lg-3 malok ' + color[i] + ' id="">' +
                '<div class="row dict">' + 
                '<div class="col-xs-9 col-sm-9 col-lg-9">' + 
                '<h3 class="term term0">' +
                lillyLivered[i].term + 
                '</div><div class="col-xs-3 col-sm-3 col-lg-3">' +
                '<div class="col-xs-3 col-sm-3 col-lg-3">' + 
                ' <span class="step size-32"><i class="icon ion-ios7-world"></i></span>' +
                '</div></div>' + 
                '<div class="row dict"><div class="col-xs-12 col-sm-12 col-lg-12">' +
                '<p class="definition definition0">' + 
                lillyLivered[i].def +
                '</p><p class="eg eg0">' + 
                lillyLivered[i].example + 
                '</p></div></div></div>'
                );
            }
        )};
    }(i));
}

The other answer also mentions a good point about lillyLivered.

Edit: Due to high demand :D, you could also use the forEach method to clean up your code and fix your issue:

lexicon.forEach(function(lexItem, i){
            $.getJSON('http://api.urbandictionary.com/v0/define?term=' + lexItem, // lexItem === lexicon[i]
                function(data){
                    lillyLivered = [];
                    lillyLivered.push(new word(data.list[0].word, data.list[0].definition, data.list[0].example));
                    console.log(lillyLivered);

                    $('.container-fluid').html('<div class="row message unread">' + 
                    ' <div class="col-xs-12 col-sm-6 col-lg-3 malok ' + color[i] + ' id="">' +
                    '<div class="row dict">' + 
                    '<div class="col-xs-9 col-sm-9 col-lg-9">' + 
                    '<h3 class="term term0">' +
                    lillyLivered[i].term + 
                    '</div><div class="col-xs-3 col-sm-3 col-lg-3">' +
                    '<div class="col-xs-3 col-sm-3 col-lg-3">' + 
                    ' <span class="step size-32"><i class="icon ion-ios7-world"></i></span>' +
                    '</div></div>' + 
                    '<div class="row dict"><div class="col-xs-12 col-sm-12 col-lg-12">' +
                    '<p class="definition definition0">' + 
                    lillyLivered[i].def +
                    '</p><p class="eg eg0">' + 
                    lillyLivered[i].example + 
                    '</p></div></div></div>'
                    );
                }
            )};
});
SimpleJ
  • 13,812
  • 13
  • 53
  • 93
  • +1, but rather than a loop with an immediately invoked anonymous function, why not use Array.forEach instead? So... `lexicon.forEach(function(item,i){dosomething();})`? Fewer braces and brackets and much easier on the eye and less prone to bugs. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach – spender May 08 '14 at 20:40
  • @spender—note that "anonymous function" is jargon for a [function expression](http://ecma-international.org/ecma-262/5.1/#sec-13). Just so you know. :-) – RobG May 08 '14 at 20:46
  • @spender I think the anonymous function makes the answer more clear. A `forEach` is cleaner and probably a better solution, but I don't think it would make understanding the issue with the original code any easier. – SimpleJ May 08 '14 at 20:48
  • Thanks @RobG. SO continues to educate! Javascript isn't my birth-language :-) – spender May 08 '14 at 20:49
  • @SimpleJ: It seems to me that (badly) keeping track of indices accounts for many bugs, but concur that Array.forEach might be more opaque to the learner. – spender May 08 '14 at 20:52
  • @spender I'll edit my answer to include an example of using a `forEach` as well. – SimpleJ May 08 '14 at 21:02
  • Thank you all, I've got a lot of reading to do. Time to learn about `forEach`. Thanks @spender and @SimpleJ! – Jeremy May 08 '14 at 21:32
1

You are redefining lillyLivered every time you call getJson so there won't be an ith term. Change this:

for (var i = 0; i < lexicon.length; i++) {

$.getJSON('http://api.urbandictionary.com/v0/define?term=' + lexicon[i],
    function(data){
        lillyLivered = [];

To:

var lillyLivered = [];
for (var i = 0; i < lexicon.length; i++) {

    $.getJSON('http://api.urbandictionary.com/v0/define?term=' + lexicon[i],
        function(data){...}
}
scrappedcola
  • 10,423
  • 1
  • 32
  • 43
  • Thanks for having a look. In the second go-round I did move the list array out of the for loop which now results in the list being created properly. http://jsfiddle.net/orenthal/5bTrJ/ In this iteration I have issues getting the information from the list to display. I'll keep futzing with it. Thanks again! – Jeremy May 08 '14 at 20:36
1

If I am reading your issue correctly you want to have all of the results displayed on the page at once and you are only seeing the last one?

if that is the issue the problem is that you are overwriting your div each time you get a new item from the api. if you change the code you have that writes the html to the dom with .html:

$('.container-fluid').html(...);

to use .append instead you will see all the results on the page:

$('.container-fluid').append(...);
  • This helps me with the div output so much. I'm still having issues with the lists but I've been given links to documentation I should read. Thanks @intelligentbean. – Jeremy May 08 '14 at 21:33