rbi-learning / Today-I-Learned

1 stars 0 forks source link

10/29 Week 3, Day 4: More APIs #188

Open ajlee12 opened 4 years ago

ajlee12 commented 4 years ago

Morning Exercise (Popeyes)

(Similar to the Ninja Turtles exercise yesterday. Refer to yesterday's notes if necessary.)

  1. Write an async function that awaits a fetch call to the API.
    • Remember to JSON-parse the data
    • Return the relevant data out of this function
  2. Write another async function that awaits the invocation of the previous function.
    • Target the HTML element, "main", within which we'll insert new <div>s.
    • Iterate over the results array and make a <div> out of each item.
      • We give these <div>s the class name "category".
    • Within the <div>:
      • add an <img> with src pointing to the image URL and alt with a brief description of the image
      • add an <h2> whose text content is the item's name
      • At the end of the iterations, insert these <div>s into the "main" element.
  3. Styling
    main {
    margin: auto;
    max-width: 60rem;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
    grid-gap: 1rem;
    }
    h1 {
    text-align: center;
    padding: 4rem;
    }

    We made the <div> into fixed sizes, but the images are spilling out due to their various and big sizes.

Let's make each image fill up only the width of each <div> that contains it:

.category img {
  width: 100%;
  height: 12rem;
}

But the images have different heights, so they were either stretched or compressed vertically.

How can we avoid that?

.category img {
  width: 100%;
  height: 12rem;

  /* Add this:  */
  object-fit: cover;
}

To remove the underline of text with links:

text-decoration: none;

Round the corners of boxes:

border-radius: 0.5rem;

What if we just want the top two corners to be rounded?

.category img {
  /* ...other properties */

  border-top-left-radius: 0.5rem;
  border-top-right-radius: 0.5rem;
}

If we want to avoid seeing the background revealed by the rounded corners, we just need to also round the same corners of the <div> that contains the image (in this case, the <div class="category">s).

Review: Video Games API

async function fetchGames(searchTerm) {
  // Disable button clicks while fetching from the API.
  button.disabled = true;
  button.textContent = 'Loading...';

  let url = ``;
  const allGames = [];

  while (url != null) {
    const response = await fetch(url);
    const data = await response.json();
    const someGames = data.results;

    const gamesWithBgImages = someGames.filter(game => game.background_image != null);
    allGames.push(...gamesWithBgImages);

    url = data.next;
  }

  // Re-enable the button clicks after fetching.
  button.disabled = false;
  button.textContent = 'Submit';

  return allGames;
}

If we want to only get the pages of the responses that have valid results:

let page = 1;

while (true) {
  let url = `api.rawg.io/api/games?search=${searchTerms}&page=${page}`;
}

Spread Operator

It's like taking the contents of an array/object and shaking them out.

const someArray = [1, 3, 5, 7];
const anotherArray = [0, ...someArray, 9];
// anotherArray => [0, 1, 3, 5, 7, 9];

const originalArray = ['a', 'b', 'c'];
const copiedArray = [...originalArray];
// copiedArray => ['a', 'b', 'c']

Yelp API Exercise Review

Remember to bring in fetch into Node on the top of the JS file:

const fetch = require('node-fetch');

In order to let Yelp know that we're authorized to use this API:

async function getRestaurants(brand, location) { const url = ${baseUrl}${brand}&location=${location};

const response = await fetch(url, { headers: { Authorization: Bearer ${process.env.YELP_API_KEY}, } });

const data = await response.json(); }

Our data comes back as an object, and within this object, we want the array stored at the `businesses` property.
So the array is essentially `data.businesses`.

Each element of this array is an object representing each business.

Let's use `.map` to create an array with objects only containing info that we want:
```js
async function getRestaurants(brand, location) {
  // other code... //

  const formattedBusinesses = data.businesses.map((business) => {
    return {
      id: business.id,
      name: business.name,
      rating: business.rating,
      latitude: business.coordinates.latitude,
      longitude: business.coordinates.longitude,
      address: business.location.display_address.join(', '),
    };
  });

  return formattedBusinesses;
}

Define another async function that finds Burger Kings in Miami:

async function logMiamiBurgerKings() {
  const miamiBKs = await getRestaurants('burger king', 'miami');
}
async function getRestaurants(brand, location) {
  const limit = 50;
  const maxBusinesses = 1000;
  const allRestaurants = [];

  // This for-loop starts from 0 and steps up by 50 after each iteration.
  // offset = 0 => 50 => 100 => 150 ...
  for (let offset = 0; offset < maxBusinesses; offset += limit) {
    const url = `${baseUrl}${brand}&location=${location}&limit=${limit}&offset=${offset}`;

    // Rest of the code... //
  }
}

Important note! This API gives you more results than just Burger King, but we're only concerned with BKs.

So let's filter out the non-BK results:

const allMatchingRestaurants = allRestaurants.filter(restaurant => {
  return (restaurant.name.toLowerCase() === brand.toLowerCase());
});

Getting form values upon submit

Let's make a form:

<form>
  <label>Brand: </label>
  <input
    id="brand"
    name="brand"
    type="text"
    required
  />
  <label>Location: </label>
  <input
    id="location"
    name="location"
    type="text"
    required
  />
  <label>API Key: </label>
  <input
    id="api-key"
    name="apiKey"
    type="text"
    required
  />
  <button type="submit">Submit</button>
</form>

It looks like this:

Screen Shot 2020-10-29 at 1 27 51 PM

Use JS to target the fields and handle the submit event:

const form = document.querySelector('form');
const tbody = document.querySelector('tbody');

async function handleSubmit(event) {
  // Prevent the default behavior of page refresh.
  event.preventDefault();

  // Target the form fields' values.
  const brand = form.brand.value;
  const location = form.location.value;
  const apiKey = form.apiKey.value;

  const restaurants = await getRestaurants(brand, location, apiKey);

  // Note that we're calling the helper function within .map
  tbody.innerHTML = restaurants.map(render).join('');
}

The helper function that renders <tr>s:

function render(restaurant) {
  return `
    <tr>
      <td>${restaurant.id}</td>
      <td>${restaurant.name}</td>
      <td>${restaurant.address}</td>
      <td>${restaurant.rating}</td>
    </tr>
  `;
}

Handling invalid inputs

What if the user types in "ebio34$T", "@#ERGIS", and "__*Y(sf889hf" in the input fields?

The API will not give an "OK" response.

So we can add a conditional check to only format incoming data that is valid.

if (response.ok) {
  // Format the restaurant data
} else {
  // Show a message on the web page to the user.
  // Return out of the loop with whatever we have in the results array.
  return allRestaurants;
}

.sort() method

This is a built-in JS function that sorts elements in an array.

To sort in ascending order:

array.sort((a, b) => a - b);

To sort in descending order:

array.sort((a, b) => b - a);

Exporting to CSV

The article Andy landed on from Google searching: https://stackoverflow.com/questions/14964035/how-to-export-javascript-array-info-to-csv-on-client-side

Write a function that processes a JS array

function exportToCSV(restaurants) {
  let csvContent = 'data:text/csv;charset=utf-8,';
  // `\n` adds a new line
  csvContent += `'id','name','address','rating'\n`;

  restaurants.forEach(restaurant => {
    // Note the comma separation and the new line.
    csvContent += `${restaurant.id},${restaurant.name},${restaurant.address},${restaurant.rating}\n`;
  });

  const encodeUri = encodeURI(csvContent);
  window.open(encodeUrl);
}

You'll be downloading a CSV file made out your array!