Search code examples
javascriptjquerytwitter-bootstrapvimeo

Vimeo images and javascript


So im having trouble getting my thumbnail system to work. It gets a image from vimeo and puts it in a . So i tried to make it loop, because that looks nicer.

But i cant get it to work :( Can somebody help me?

            <div class="row">
            <div class="col-sm-3"><img src="" id="img1" class="img-rounded" width="100%" height="200px"></img></div>
            <div class="col-sm-3"><img src="" id="img2" class="img-rounded" width="100%" height="200px"></img></div>
            <div class="col-sm-3"><img src="" id="img3" class="img-rounded" width="100%" height="200px"></img></div>
            <div class="col-sm-3"><img src="" id="img4" class="img-rounded" width="100%" height="200px"></img></div>
        </div>
        <br>
        <div class="row">
            <div class="col-sm-3"><img src="" id="img5" class="img-rounded" width="100%" height="200px"></img></div>
            <div class="col-sm-3"><img src="" id="img6" class="img-rounded" width="100%" height="200px"></img></div>
            <div class="col-sm-3"><img src="" id="img7" class="img-rounded" width="100%" height="200px"></img></div>
            <div class="col-sm-3"><img src="" id="img8" class="img-rounded" width="100%" height="200px"></img></div>
        </div>
    </div>
</body>

<script>
    var ids = ["169971394", "169657641", "169569693", "169569661", "169569619", "169569539", "169569509", "169566439"]
    var imgs= ["img1", "img2", "img3", "img4", "img5", "img6", "img7", "img8"]
    for (i = 0; i < 7; i++) {
        $(document).ready(function () {
            var vimeoVideoUrl = 'https://player.vimeo.com/video/' + ids[i];
            console.log(vimeoVideoUrl);
            var match = /vimeo.*\/(\d+)/i.exec(vimeoVideoUrl);
            if (match) {
                var vimeoVideoID = match[1];
                $.getJSON('http://www.vimeo.com/api/v2/video/' + vimeoVideoID + '.json?callback=?', { format: "json" }, function (data) {
                    featuredImg = data[0].thumbnail_large;
                    console.log(imgs[i]);
                    $(imgs[i]).attr("src", featuredImg);
                });
            }
        });
        }
</script>

(Above is the important part.) FYI: im using bootstrap and jquery for this. (And some other things, but those are not important)


Solution

  • Some Background

    The complication with doing this sort of thing in a loop is that $.getJSON() is asynchronous, and for loops are synchronous. For a basic understanding of the difference, check out this answer. But here's the general idea:

    Synchronous: Things are executed in sequence. When one operation finishes, the next one starts.

    Example:

    var a = 0; // This is executed first
    a = a + 1; // This is executed second
    

    Asynchronous: The synchronous operations do not wait for asynchronous operations to complete before continuing with the script

    Example:

    // This is executed first
    var a = 0;
    
    // This is executed 2nd, but the function inside won't be called for 1 second
    setTimeout(function() {
      // this is executed last, outputs 1
      console.log(a);
    }, 1000);
    
    // this is executed third, before the function call
    a = a + 1;

    Your Problem

    In your example code, you are executing an asynchronous function inside of a synchronous operation. Normally, this is okay, and you don't really have to worry about it. The issue arises when you try to use the variable i in your asynchronous function, but the variable i is changed during your synchronous operation (the for loop).

    Here is an example to display what is happening

    for (var i = 0; i < 5; i++) {
      setTimeout(function() {
        console.log(i);
      }, 1000)
    }

    Thinking about it

    Now, by looking at the code you might expect it to wait 1 second, then log each number to the console, something like

    -> 0
    -> 1
    -> 2
    -> 3
    -> 4
    

    The Catch

    However, if you actually run the snippet above, you will see that it logs 5 every time. This is because the variable i actually has a value of 5 when the callback functions actually run.

    But how?

    How? The for loop continued execution before each setTimout() finished it's 1 second wait. So by the time that 1 second was over, the loop had already incremented i to the point that broke the loop, which was 5 in this case.

    In your problem, a simple console.log(i); during your callback function will most likely show 7 eight times because the variable i is still being incremented while your $.getJSON() is actually waiting for the results to be returned. When the results actually are returned, the loop is probably done running, and i will equal 7

    The Solution

    In order to be sure that we are putting the videos and images in the correct order on the page and that we are actually putting each image in the right place, we need to do some hefty workarounds for this asynchronous behavior. I've commented this code to give you an idea of what each line is doing.

    // dom is ready to be manipulated
    $(document).ready(function() {
      // create an array of our video ids
      var ids = [
        "169971394",
        "169657641",
        "169569693",
        "169569661",
        "169569619",
        "169569539",
        "169569509",
        "169566439"
      ];
      // initialize an empty array that will eventually hold our videos
      var videos = [];
      // loop through our array of ids
      ids.forEach(function(id, i) {
        // because these are in order, ids[0] should be in #img1 etc
        var image = '#img' + (i + 1);
    
        // get your video url --not actually used in this code
        var vimeoVideoUrl = 'https://player.vimeo.com/video/' + id;
    
        // create an object that holds the information for this video that can
        // be gathered *synchronously*
        var v = {
          id: id,
          image: '#img' + (i + 1),
          video: vimeoVideoUrl
        };
    
        // add this object to our initially empty array
        videos.push(v);
    
        /* after the loop ends, videos array might look like this
    
        [
         {
           id: "169971394",
           image: "#img1",
           video: "https://player.vimeo.com/video/169971394"
         },
         {
           ... next video
         } ... for each video
        ]
    
        */
        // do our ajax operation to get the rest of the video information
        $.getJSON(
          'https://www.vimeo.com/api/v2/video/' + id + '.json?callback=?', {
            format: "json"
          },
          // REMEMBER -- this function is *asynchronous* and we can't trust the
          // value of the increment from our loop, so this is relatively complex
          function(data) {
            // get the id of the returned video
            var id = data[0].id;
    
            // get the thumbnail
            var thumb = data[0].thumbnail_large;
    
            // loop through our videos array
            videos.forEach(function(v, i) {
              // find the element of the array with the id that was just returned
              if (v.id == id) {
                // and add the thumbnail url to the object
                videos[i].thumbnail = thumb;
    
                /* an element of the videos array will look like this when the callback
                   has finished for that video
    
                {
                  id: "169971394",
                  image: "#img1",
                  video: "https://player.vimeo.com/video/169971394",
                  thumbnail: "https://i.vimeocdn.com/video/574879586_640.jpg"
                }
    
                  notice that the thumbnail property is now set
                */
              }
            });
    
            // each time we execute the callback, we need to know if it's the last 
            // execution, so we will need to check if every callback has been completed
    
            // this condition returns false if the thumbnail element is not assigned
            // to every object in the videos array, which would tell us that this isn't
            // the last execution of the callback
            if (videos.every(function(v) {
              return v.thumbnail !== undefined
            })) {
              // if this is the last execution of the callback,
              // loop through each item in the videos array
              videos.forEach(function(video) {
                // set the src of the video's corresponding image to the url of the thumbnail
                $(video.image).attr("src", video.thumbnail);
              });
            }
          }
        );
      });
    });
    

    https://jsfiddle.net/o82L6q48/2/ to see this in action

    Note that this may be more complicated than it actually needs to be, but it works, and I hope that you learn something from it.