Wanderlust photo retrieval challenge - Has anybody tried it?


#1

Link: https://www.codecademy.com/courses/introduction-to-javascript/projects/wanderlust

Hi, did anybody have a look at the “real” challenge at exercise 43 (photo retrieval)?

I tried to, but did not (yet) succeed.

Pain points:

  • The text says that an extra request is necessary, as the photo information is not returned in the getVenues() request. I don’t agree here. Each venue in response.groups[0].items (which is what gets returned by getVenues()) has a photos object. This object has a count property and an array of photos. So one could access that information without a further request. Problem is that the photo array is always empty, even for big cities like New York, London, or Berlin.

  • The documentation of the FourSquare API says that the url to get at the photos is “https://api.foursquare.com/v2/venues/VENUE_ID/photos”, where a valid instance of VENUE_ID would be any venue ID found in response.groups[0].items. So I guess that I’ll have to create a new request using this URL despite the fact that getVenues() returns a ‘photos’ object. I’d like a hint on how to construct the URL (include all the different venue IDs from getVenues() in the URL)

  • When I try the URL from bullet 2 with a real instance of a venue ID, I get an authentication error despite the fact that I seem to be logged into FourSquare. The JavaScript console reports:
    GET https://api.foursquare.com/v2/venues/4adcda7cf964a520524721e3/photos 400. This resolves to: {

  • meta: {

  • response: { }

}

Many thanks in advance and best wishes from Germany

Frank

I’m getting closer to the solution. You have to submit the client ID and client secret if you want to get at the photos. That makes it:

await fetch (https://api.foursquare.com/v2/venues/${venue.id}/photos?&client_id=${clientId}&client_secret=${clientSecret}&v=20181114);

Console output for jsonResponse is:

  1. response:

  2. photos:

  3. count: 2

  4. dupesRemoved: 0

  5. items: Array(2)

  6. 0:

  7. checkin: {id: “51e99698498e4d66c82c8369”, createdAt: 1374262936, type: “checkin”, timeZoneOffset: 120}

  8. createdAt: 1374263266

  9. height: 612

  10. id: “51e997e2498ecf3311b6114d”

  11. prefix: “https://fastly.4sqi.net/img/general/

  12. source:

  13. name: “Instagram”

  14. url: “http://instagram.com

  15. proto: Object

  16. suffix: “/22434487_QY5x6l3S58BxyFqFfw0ZMA-Ck34AayCwBF56l1ihJs0.jpg”

  17. user: {id: “22434487”, firstName: “Olga”, lastName: “Tarasova”, gender: “female”, photo: {…}}

  18. visibility: “public”

  19. width: 612

Looks promising …

Frank.


#2

I am trying for the same thing, but I have a problem with where to do the call for photos, and how to retrieve the venueIds from venues to loop over them and requesting photos from the assembled url. Could you show me how you solved it? Currently my code looks like, well, a mess (I would post it here but for some reason I can’t connect to the Codecademy course pages).


#3

Hi Jenny,
problem is I won’t have time for coding practices this week. But let me tell you how far I got:

  • The venue IDs: You get them from the first object you retrieved (Foursquare).
    Inside that object, they are located at
    response.groups[0].items.venue.id

  • The photos are actually in a different branch of the object, and weren’t, as they’ve said in the exercise description, retrieved in the first request. The photos are at response.photos. To get at them, you need to include the dimensions (in pixels) in the directory path of the URL you’re creating. I found that out by chance.

So you first call the photos branch of the object in your request with:
https://api.foursquare.com/v2/venues/${venue.id}/photos?&client_id=${clientId}&client_secret=${clientSecret}&v=20181114;

From the JSON you receive, you create the photo retrieval URL as follows:
const photoUrl = https://fastly.4sqi.net/img/general/200x200${jsonResponse.response.photos.items[0].suffix};

Now, I did not yet succeed in displaying the photos in the web app, but since you get all venue photos from the request, and probably cannot display all of them, and since furthermore, you have to pick the right set of photos for each venue, my idea was to call the getPhotos() request from the renderVenues() function, as part of venueContent = createHTML.

I thought so because renderVenues loops over all the individual venues.

I hope this helps. Let me know if you can make any progress from there.

Bye,

Frank.


#4

I am at that exact same spot; calling the photo function from within renderVenues, and with no success at displaying them. I guess I am missing something within that rendering function; I return the first photoitem from the photos within getVenuePhotos, but the returned photo object seemingly returns into nothing, and the url assembly reads as undefinedSIZEundefined.

The getVenuePhotos code:

const getVenuePhotos = async (venueId) => {
const photoUrlToFetch = `${photoUrl}${venueId}/photos?limit=1&client_id=${clientId}&client_secret=${clientSecret}&v=20181106`;
  try {
  const response = await fetch(photoUrlToFetch);
  if(response.ok) {
    const jsonResponse = await response.json();
    console.log(jsonResponse);
    const photo = jsonResponse.response.photos.items[0];
    console.log(photo.prefix);
    console.log(photo.suffix);
    const venuePhotoSrc = `${photo.prefix}200x100${photo.suffix}`;
    console.log(venuePhotoSrc);
    return venuePhotoSrc;
  }
  else {
    throw new error('Request failed!');
  }
} catch(error) {
  console.log(error.message);
}}



The render and the rest of the code, if anyone sees where I have missed to hook up photo:

const renderVenues = (venues) => {
    $venueDivs.forEach(($venue, index) => {
    const venue = venues[index];
    const venueId = venue.id;
    const venueIcon = venue.categories[0].icon;
    const venueImgSrc = `${venueIcon.prefix}bg_100${venueIcon.suffix}`;
    getVenuePhotos(venueId);
    
    let venueContent = createVenueHTML(venue.name, venue.location, venueImgSrc, venuePhotoSrc);
    $venue.append(venueContent);
   
  });
  $destination.append(`<h2>${venues[0].location.city}</h2>`);
}


const executeSearch = () => {
  $venueDivs.forEach(venue => venue.empty());
  $weatherDivs.forEach(day => day.empty());
  $destination.empty();
  $container.css("visibility", "visible");
  getVenues().then(venues => renderVenues(venues));
  getForecast().then(forecast => renderForecast(forecast));
  return false;
}

$submit.click(executeSearch)


#5

Hi Jenny,
problem solved. I want to be honest here: a JavaScript-adept colleague helped me with this. I always got the right kind of output from my getPhoto function, but called from the outside of the function, it always returned a promise, and not the URLs I wanted. It almost drove me nuts yesterday.

But I had forgotten just a single keyword inside of renderVenues(): await, cause getPhotos is an async function and you have to treat it like that no matter from where it is called.

So, called from renderVenues, the line reads:

let photos = await getPhotos(venue.id);

It then returns the proper photo URLs which can use as values of the src property inside of the HTML.

Here’s my entire main.js code, without the keys at the beginning:

// Page Elements
const input = (’#city’);
const submit = (’#button’);
const destination = (’#destination’);
const container = (’.container’);
const venueDivs = [("#venue1"), ("#venue2"), ("#venue3"), ("#venue4"), ("#venue5")];
const weatherDivs = [("#weather1"), ("#weather2"), ("#weather3"), $("#weather4")];
const weekDays = [‘Sunday’, ‘Monday’, ‘Tuesday’, ‘Wednesday’, ‘Thursday’, ‘Friday’, ‘Saturday’];

// Add AJAX functions here:
const getVenues = async() => {
const city = input.val(); const urlToFetch = `{url}{city}&limit=10&client_id={clientId}&client_secret=${clientSecret}&v=20181126`;
try {
const response = await fetch(urlToFetch);
if(response.ok){
const jsonResponse = await response.json();
// console.log(jsonResponse);
const venues = jsonResponse.response.groups[0].items.map(parameter => parameter.venue);
console.log(Array.isArray(venues));

   // console.log(venues);
   return venues;
    
}

}
catch(error) {
console.log(error);
}
}

const getPhotos = async(inp) => {
try {
const response = await fetch (https://api.foursquare.com/v2/venues/${inp}/photos?&client_id=${clientId}&client_secret=${clientSecret}&v=20181126);

    if(response.ok){
  const jsonResponse = await response.json();
  // console.log(jsonResponse);
   
      let photoUrl = `https://fastly.4sqi.net/img/general/200x200${jsonResponse.response.photos.items[0].suffix}`;
     
      return photoUrl;
      
     
}

}
catch(error) {
console.log(error);
}

}

const getForecast = async() => {

const urlToFetch = ${forecastUrl}${apiKey}&q=${$input.val()}&days=4&hour=11;

try {
const response = await fetch(urlToFetch);
if(response.ok){
const jsonResponse = await response.json();
// console.log(jsonResponse);
const days = jsonResponse.forecast.forecastday;

 return days;

}
}
catch(error) {
console.log(error);
}

}

// Render functions
// Shuffle the venues array using the Fisher Yates
// shuffle:
const venueShuffle = function (venues) {

for (let i= venues.length -1; i > 0; i–) {
let idx = Math.floor(Math.random() * (i + 1));
let tmp = venues[i];
venues[i] = venues[idx];
venues[idx] = tmp;
};
return venues;
};

const renderVenues = (venues) => {
// call venueShuffle to get a different,
// random order
venueShuffle(venues);

$venueDivs.forEach(async ($venue, index) => {
// Add your code here:
const venue = venues[index];

const venueIcon = venue.categories[0].icon;
// console.log(venueIcon);
const venueImgSrc = `${venueIcon.prefix}bg_64${venueIcon.suffix}`;

 let photos = await getPhotos(venue.id);
console.log(photos);


let venueContent = createVenueHTML(venue.name, venue.location, venueImgSrc, photos);
$venue.append(venueContent);

});
destination.append(`<h2>{venues[0].location.city}`);
}

const renderForecast = (days) => {
$weatherDivs.forEach(($day, index) => {
// Add your code here:
const currentDay = days[index];
let weatherContent = createWeatherHTML(currentDay);
$day.append(weatherContent);
});
}

const executeSearch = () => {
$venueDivs.forEach(venue => venue.empty());
$weatherDivs.forEach(day => day.empty());
$destination.empty();
$container.css(“visibility”, “visible”);
getVenues().then(venues => renderVenues(venues));
//
// getPhotos().then(photos => // renderVenues(photos));
getForecast().then(forecast => renderForecast(forecast));
return false;
}

$submit.click(executeSearch)


#6

YAAAAAAAY! I had come so far as to get that there was something wrong in the rendering function AND asking people more knowing in JS - and to my delight I see that Codecademy has a new JS async course. Hopefully we’ll get a better grasp on this with that one. THANK YOU so much for this!