jQuery getJSON $.each load code when loop finishes?

I’ve been trying to tackle this for quite some time now and can’t figure out how to do it hence me posting this question!

This code below loads in all my flickr images successfully -


	 jQuery(document).ready(function($) {

                var apiKey = '??????';
                $.getJSON('http://api.flickr.com/services/rest/?&method=flickr.photosets.getPhotos&api_key=' + apiKey + '&photoset_id=<?php echo $this->getPhotosetId($_photo) ?>&format=json&jsoncallback=?',
                function(data){

                    //loop through the results with the following function
                    $.each(data.photoset.photo, function(i,item){

                        //build the url of the photo in order to link to it
                        var photoURL = 'http://farm' + item.farm + '.static.flickr.com/' + item.server + '/' + item.id + '_' + item.secret + '_m.jpg';

                        //turn the photo id into a variable
                        var photoID = item.id;

                        //use another ajax request to get the tags of the image
                        $.getJSON('http://api.flickr.com/services/rest/?&method=flickr.photos.getInfo&api_key=' + apiKey + '&photo_id=' + photoID + '&format=json&jsoncallback=?',
                        function(data){

                        var imgCont = '<div class="col-sm-3"><img src="' + photoURL + '"></div>';

                        $(imgCont).appendTo('.carousel-inner');

                        });

                  });//each

                });

        });

What I want to do is when all the images have loaded is run this bit of code -


jQuery(document).ready(function($) {
                            var divs = $(".carousel-inner .col-sm-3");
                            for(var i = 0; i < divs.length; i+=4) {
                              divs.slice(i, i+4).wrapAll("<div class='item'><div class='row'></div></div>");
                            }
                            $('.item').first().addClass('active');
       });

When I place that last chunk of code into the getjson $.each loop the .wrapAll function gets added to every iteration but I just want .wrapAll to be applied when the loop has finished performing. How can this be done?

Hi there,

Can you not just place it within the success callback, but outside of the $.each loop?

e.g.

$.getJSON(
  'http://api.flickr.com/services/rest/?&method=flickr.photosets.getPhotos&api_key=' + apiKey + '&photoset_id=<?php echo $this->getPhotosetId($_photo) ?>&format=json&jsoncallback=?',
  function(data){
    $.each(data.photoset.photo, function(i,item){
      ...
    }); // End each

    var divs = $(".carousel-inner .col-sm-3");
    for(var i = 0; i < divs.length; i+=4) {
      divs.slice(i, i+4).wrapAll("<div class='item'><div class='row'></div></div>");
    }
    $('.item').first().addClass('active');

); // End getJSON

That wouldn’t work, because the images are only inserted into the page in the callback for the second ajax request (within the loop). That last piece of code which divides up the photos into rows would execute before the images were inserted.

EDIT: Shtoom, what’s the purpose of the second ajax request anyway? You don’t seem to be doing anything with the result.

The second ajax request gets the photos from flickr and appends the result to <div class=“carousel-inner”> using the $.each loop inside the second request to find all photos.

How can I store/save all the loop results first before appending them to <div class=“carousel-inner”>? Can I get all the results after the loop finishes then I can apply the wrapAll function to the requested html?

Isn’t there a way of finding out when the last iteration has completed and then get the all the html that was requested?

When I add an alert outside the $.each loop it returns empty but if I add it inside the loop it will first alert empty and then keep alerting me every time a new photo is added, the last alert will have all the images/html inside it. If I can somehow execute the .wrapall function to the last iteration to all of the html once then I think my problem will be solved.

alert($('.carousel-inner').html())

I have tried adding the success callback but that doesn’t seem to work either :confused:

$.getJSON('http://api.flickr.com/services/rest/?&method=flickr.photos.getInfo&api_key=' + apiKey + '&photo_id=' + photoID + '&format=json&jsoncallback=?',
    function(data){
        var imgCont = '<div class="col-sm-3"><img src="' + photoURL + '"></div>';
        $(imgCont).appendTo('.carousel-inner');
});

You’re not actually doing anything with the data that is returned by the second ajax request, so you could get rid of it and just do this:

var apiKey = '??????';
$.getJSON('http://api.flickr.com/services/rest/?&method=flickr.photosets.getPhotos&api_key=' + apiKey + '&photoset_id=<?php echo $this->getPhotosetId($_photo) ?>&format=json&jsoncallback=?',
    function(data){

        //loop through the results with the following function
        $.each(data.photoset.photo, function(i,item){
            //build the url of the photo in order to link to it
            var photoURL = 'http://farm' + item.farm + '.static.flickr.com/' + item.server + '/' + item.id + '_' + item.secret + '_m.jpg';
            $('<div class="col-sm-3"><img src="' + photoURL + '"></div>').appendTo('.carousel-inner');
        });

        var divs = $(".carousel-inner .col-sm-3");
        for(var i = 0; i < divs.length; i+=4) {
            divs.slice(i, i+4).wrapAll("<div class='item'><div class='row'></div></div>");
        }
        $('.item').first().addClass('active');
    });

Hi fretburner,

I do actually need the second ajax request so I can get the photo info because the second request gives me the title and description of the photos.

The code you provided works but it doesn’t generate the titles now, apologise I did not answer this correctly first time I actually removed the title from my first example to simplify my question. I’ve now added the title to the appending html you provided but because the second ajax request isn’t present the title doesn’t appear any more?

 $('<div class="col-sm-3"><img src="' + photoURL + '"><h2>' + data.photo.title._content + '</h2></div>').appendTo('.carousel-inner');

Is there anyway I can merge both getJSON requests so the title will also load?

 $.getJSON('http://api.flickr.com/services/rest/?&method=flickr.photosets.getPhotos&api_key=' + apiKey + '&photoset_id=<?php echo $this->getPhotosetId($_photo) ?>&format=json&jsoncallback=?',
 $.getJSON('http://api.flickr.com/services/rest/?&method=flickr.photos.getInfo&api_key=' + apiKey + '&photo_id=' + photoID + '&format=json&jsoncallback=?',

The title is included in the data returned from the first ajax request, you’d just need to change this line from the code I posted:

$('<div class="col-sm-3"><img src="' + photoURL + '"><h2>' + item.title + '</h2></div>').appendTo('.carousel-inner');

If you want the description as well, then you would need to make the additional API call for each image. In that case, you could do something like this:

var apiKey = '????';

$.getJSON('http://api.flickr.com/services/rest/?&method=flickr.photosets.getPhotos&api_key=' + apiKey + '&photoset_id=1713229&format=json&jsoncallback=?')
    .then(function(data){
        return $.when.apply(window, $.map(data.photoset.photo, function(item) {
            //build the url of the photo in order to link to it
            var photoURL = 'http://farm' + item.farm + '.static.flickr.com/' + item.server + '/' + item.id + '_' + item.secret + '_m.jpg',
                photoID = item.id;

            return $.getJSON('http://api.flickr.com/services/rest/?&method=flickr.photos.getInfo&api_key=' + apiKey + '&photo_id=' + photoID + '&format=json&jsoncallback=?')
                .then(function(data){
                    $('<div class="col-sm-3"><img src="' + photoURL + '"><h2>' + data.photo.title._content + '</h2></div>').appendTo('.carousel-inner');
                });
        }));
    }).done(function(){
        var divs = $(".carousel-inner .col-sm-3");
        for(var i = 0; i < divs.length; i += 4) {
            divs.slice(i, i+4).wrapAll("<div class='item'><div class='row'></div></div>");
        }
        $('.item').first().addClass('active');
    });

(Note: the code has only been tested with the lastest version of jQuery)

This can be a little tricky to follow if you’ve not worked with jQuery promises before. Basically, the ajax requests return ‘promise’ objects which you can attach callbacks to which will fire on success / failure (in the same way as the success / failure callbacks that you can pass directly to jQuery’s ajax functions).

Promises can be chained using the .then() method, to allow you to create a chain of functions which will execute in sequence, each one passing it’s result to the next for further processing.

The .when() function allows you to pass any number of promises as arguments, and returns a single promise which is resolved only when all the others are resolved.

My explanation glosses over a lot of detail, so you should checkout the jQuery docs on those functions if you want to know more about how they work.

wow yes this works perfectly! I did stumble upon the jQuery promises yesterday and did try adding then and when but was only second guessing had no idea how to properly implement them. Thanks for teaching me your explanation is very good something I won’t forget!

Thinking outloud now but what I want to do next is extract a link from the description text so jquery find text string http://www.???.com/???/ and move the link into the href the button. I am guessing I can add this into the done sequence?

Will post up anything else I can think of.

Thanks again!

I’ve just noticed the photos now load randomly the order doesn’t match the photoset is this because the $.each loop is not being used anymore?

I have added extras=date_taken to the flickr.photosets.getPhotos json url but not sure how to sort the results by date_taken? Any ideas how this can be achieved?

Hi shtoom,

I’ve made some changes to the code so that it sorts the results by date_taken, but I also decided to try and tidy up the code a little as it was starting to get messy:

<script src="flickrAPI.js"></script>
<script type="text/javascript">
    function parseDateTime(dateTime) {
        var t = dateTime.split(/[- :]/);
        return new Date(t[0], t[1]-1, t[2], t[3], t[4], t[5]);
    };

    function renderPhotos(photos) {
        var container = $('.carousel-inner');

        $.each(photos, function(index, photo) {
            var photoURL = 'http://farm' + photo.farm + '.static.flickr.com/' + photo.server + '/' + photo.id + '_' + photo.secret + '_m.jpg';
            container.append('<div class="col-sm-3"><img src="' + photoURL + '"><h2>' + photo.title._content + '</h2></div>');
        });
            
        var divs = container.find('.col-sm-3');
        for(var i = 0; i < divs.length; i += 4) {
            divs.slice(i, i+4).wrapAll("<div class='item'><div class='row'></div></div>");
        }

        divs.find('.item').first().addClass('active');
    };

    var flickrApi = new FlickrApi('????');
    var photosetId = <?php echo $this->getPhotosetId($_photo) ?>;

    flickrApi
        .getPhotoset(photosetId)
        .then(function(data){
            return $.when.apply(window, $.map(data.photoset.photo, function(item) {
                return flickrApi.getPhotoInfo(item.id);
            }));
        })
        .then(function(){
            var photos = $.map(arguments, function(argument) { return argument[0].photo });
            photos.sort(function(a, b) {
                var dateA = parseDateTime(a.dates.taken),
                    dateB = parseDateTime(b.dates.taken);

                if (dateA.getTime() < dateB.getTime())
                    return -1;
                if (dateA.getTime() > dateB.getTime())
                    return 1;
                return 0;
            });

            renderPhotos(photos);
        });
</script>

You’ll notice I’ve included a script called flickrAPI.js at the top - here is the code for that:

function FlickrApi(apiKey) {
    this.apiKey = apiKey;
}

FlickrApi.prototype = {
    constructor: FlickrApi,

    baseUrl: 'http://api.flickr.com/services/rest/?jsoncallback=?',

    format: 'json',

    getPhotoset: function(photosetId) {
        var params = {
            api_key:      this.apiKey,
            method:       'flickr.photosets.getPhotos',
            photoset_id:  photosetId,
            format:       this.format
        };

        return this._doRequest(params);
    },

    getPhotoInfo: function(photoId) {
        var params = {
            api_key:      this.apiKey,
            method:       'flickr.photos.getInfo',
            photo_id:     photoId,
            format:       this.format
        };

        return this._doRequest(params);
    },

    _doRequest: function(params) {
        return $.getJSON(this.baseUrl, params);
    }
}

The reason for creating this object is two-fold: First, it removes those long $.getJSON lines from the code and replaces them with short method calls which have more explanatory names. Second, it prevents the rest of your code from having to know how the flickr API works, which means if it ever changes, you only need to modify this object. It might not seem that necessary given the size of the script we’re dealing with here, but it’s good practice and you could easily re-use it in other apps.

Hi I can’t seem to get this to work now the images don’t load? I haven’t modified the object code but I changed this line to my API ID #

 var flickrApi = new FlickrApi('????');

but nothing loads inside

var container = $('.carousel-inner');

I’ve tried wrapping the JS after the object with jQuery ready function because the .wrapall function is there but still nothing…

This is how it all looks

<script>

function FlickrApi(apiKey) {
    this.apiKey = apiKey;
}
 
FlickrApi.prototype = {
    constructor: FlickrApi,
 
    baseUrl: 'http://api.flickr.com/services/rest/?jsoncallback=?',
 
    format: 'json',
 
    getPhotoset: function(photosetId) {
        var params = {
            api_key:      this.apiKey,
            method:       'flickr.photosets.getPhotos',
            photoset_id:  photosetId,
            format:       this.format
        };
 
        return this._doRequest(params);
    },
 
    getPhotoInfo: function(photoId) {
        var params = {
            api_key:      this.apiKey,
            method:       'flickr.photos.getInfo',
            photo_id:     photoId,
            format:       this.format
        };
 
        return this._doRequest(params);
    },
 
    _doRequest: function(params) {
        return $.getJSON(this.baseUrl, params);
    }
}

//end object

  function parseDateTime(dateTime) {
        var t = dateTime.split(/[- :]/);
        return new Date(t[0], t[1]-1, t[2], t[3], t[4], t[5]);
    };
 
    function renderPhotos(photos) {
        var container = $('.carousel-inner');
 
        $.each(photos, function(index, photo) {
            var photoURL = 'http://farm' + photo.farm + '.static.flickr.com/' + photo.server + '/' + photo.id + '_' + photo.secret + '_m.jpg';
            container.append('<li class="col-sm-3"><img src="' + photoURL + '"><h2>' + photo.title._content + '</h2></li>');
        });
 
        var divs = container.find('.col-sm-3');
        for(var i = 0; i < divs.length; i += 4) {
            divs.slice(i, i+4).wrapAll("<div class='item'><div class='row'></div></div>");
        }
 
        divs.find('.item').first().addClass('active');

    };
 
    var flickrApi = new FlickrApi('1234');

    var photosetId = <?php echo $this->getPhotosetId($_photo) ?>;
 
    flickrApi
        .getPhotoset(photosetId)
        .then(function(data){
            return $.when.apply(window, $.map(data.photoset.photo, function(item) {
                return flickrApi.getPhotoInfo(item.id);
            }));
        })
        .then(function(){
            var photos = $.map(arguments, function(argument) { return argument[0].photo });
            photos.sort(function(a, b) {
                var dateA = parseDateTime(a.dates.taken),
                    dateB = parseDateTime(b.dates.taken);
 
                if (dateA.getTime() < dateB.getTime())
                    return -1;
                if (dateA.getTime() > dateB.getTime())
                    return 1;
                return 0;
            });
 
            renderPhotos(photos);
        });
    
    
    
    

  </script>
       

Do you get any error messages in the browser console?

Yes this is the error

TypeError: data.photoset is undefined

When I replace this


    var flickrApi = new FlickrApi('1234');

    var photosetId = <?php echo $this->getPhotosetId($_photo) ?>;
 
    flickrApi
        .getPhotoset(photosetId)
        .then(function(data){
            return $.when.apply(window, $.map(data.photoset.photo, function(item) {
                
                return flickrApi.getPhotoInfo(item.id);
            }));
        })

with

var apiKey = '1234';
    
$.getJSON('http://api.flickr.com/services/rest/?&method=flickr.photosets.getPhotos&api_key=' + apiKey + '&photoset_id=<?php echo $this->getPhotosetId($_photo) ?>&extras=date_taken&format=json&jsoncallback=?')
    .then(function(data){
        return $.when.apply(window, $.map(data.photoset.photo, function(item) {
            //build the url of the photo in order to link to it
            var photoURL = 'http://farm' + item.farm + '.static.flickr.com/' + item.server + '/' + item.id + '_' + item.secret + '_z.jpg',
                photoID = item.id;
                
           
 
 
            return $.getJSON('http://api.flickr.com/services/rest/?&method=flickr.photos.getInfo&api_key=' + apiKey + '&photo_id=' + photoID + '&extras=date_taken&format=json&jsoncallback=?')
                .then(function(data){
                    $('<li class="col-sm-3"><img src="' + photoURL + '" alt="' + data.photo.title._content + '"><div class="caption"><h3>' + data.photo.title._content + '</h3><p>' + data.photo.description._content + '</p></div></li>').appendTo('.carousel-inner');
                });
                
             
        }));
    })

The images load again but still randomly.

Is the page online somewhere where I could take a look? I’ve got the code set up in a test page on my machine and it seems to work ok, so there might be something we’re missing with the way your page is set up.

*I modified my previous post…

Its on my local machine its a bit of a pain the page is inside magento :confused:

This is how the markup is looking my end.

<script>
function FlickrApi(apiKey) {
    this.apiKey = apiKey;
}
 
FlickrApi.prototype = {
    constructor: FlickrApi,
 
    baseUrl: 'http://api.flickr.com/services/rest/?jsoncallback=?',
 
    format: 'json',
 
    getPhotoset: function(photosetId) {
        var params = {
            api_key:      this.apiKey,
            method:       'flickr.photosets.getPhotos',
            photoset_id:  photosetId,
            format:       this.format
        };
 
        return this._doRequest(params);
    },
 
    getPhotoInfo: function(photoId) {
        var params = {
            api_key:      this.apiKey,
            method:       'flickr.photos.getInfo',
            photo_id:     photoId,
            format:       this.format
        };
 
        return this._doRequest(params);
    },
 
    _doRequest: function(params) {
        return $.getJSON(this.baseUrl, params);
    }
}
    
    jQuery(document).ready(function($) {
    

  function parseDateTime(dateTime) {
        var t = dateTime.split(/[- :]/);
        return new Date(t[0], t[1]-1, t[2], t[3], t[4], t[5]);
    };
 
    function renderPhotos(photos) {
        var container = $('.carousel-inner');
 
        $.each(photos, function(index, photo) {
            var photoURL = 'http://farm' + photo.farm + '.static.flickr.com/' + photo.server + '/' + photo.id + '_' + photo.secret + '_m.jpg';
            container.append('<li class="col-sm-3"><img src="' + photoURL + '"><h2>' + photo.title._content + '</h2></li>');
        });
 
        var divs = container.find('.col-sm-3');
        for(var i = 0; i < divs.length; i += 4) {
            divs.slice(i, i+4).wrapAll("<div class='item'><div class='row'></div></div>");
        }
 
        divs.find('.item').first().addClass('active');
        alert('test');
    };
 
    var flickrApi = new FlickrApi('1234');

    var photosetId = <?php echo $this->getPhotosetId($_photo) ?>;
 
    flickrApi
        .getPhotoset(photosetId)
        .then(function(data){
            return $.when.apply(window, $.map(data.photoset.photo, function(item) {
                
                return flickrApi.getPhotoInfo(item.id);
            }));
        })
        .then(function(){
            var photos = $.map(arguments, function(argument) { return argument[0].photo });
            photos.sort(function(a, b) {
                var dateA = parseDateTime(a.dates.taken),
                    dateB = parseDateTime(b.dates.taken);
 
                if (dateA.getTime() < dateB.getTime())
                    return -1;
                if (dateA.getTime() > dateB.getTime())
                    return 1;
                return 0;
            });
 
            renderPhotos(photos);
        });
    });

  </script>
  
<div class="wrap-carousel">
<button class="left carousel-control prev"><i class="icon-angle-left"></i></button>
<button class="right carousel-control next"><i class="icon-angle-right"></i></button>
        <div id="flickr-carousel">
            <ul class="carousel-inner">
            </ul>
        </div> 
</div>

I’ve copied and pasted the code from your last post into JS Fiddle (just changing the API key) and it works ok for me. What version of jQuery are you using?

I am using this version http://code.jquery.com/jquery-1.10.2.min.js

I have created two jsfiddle links -
working ‘old’ version without the object http://jsfiddle.net/w47n8/1/

new version with the object method but not loading? http://jsfiddle.net/GR8sX/1/

Cool got it working! The jsfiddle error message is different to my local

jQuery - missing } after function body
I had to close the jquery body }); oops!

Here’s a working link! http://jsfiddle.net/GR8sX/3/

Thanks so much again hopefully that is it for now!