The Code

const API_KEY = "SECRET";

// Calls the API and gets the movies, async tells it to wait, returns movie array
async function getMovies() {

    // attempt to get all the popular movies through API
    try {
        let response = await fetch('https://api.themoviedb.org/3/movie/popular', {
            headers: {
                'Authorization': `Bearer ${API_KEY}`
            }
        });

        // changes the string recieved back and parses it into an object
        let data = await response.json();

        return data.results;

    // if something goes wrong, show error alert
    } catch (error) {
        swal.fire({
            icon: 'error',
            backdrop: false,
            title: 'Oops!',
            text: 'Something went wrong reaching the TMBD API.'
        })
    }
}

// Calls the API and gets information for the one movie
async function getMovie(movieId) {

    // Attempt to get movie details through API
    try {
        let response = await fetch(`https://api.themoviedb.org/3/movie/${movieId}`, {
            headers: {
                'Authorization': `Bearer ${API_KEY}`
            }
        });

        // changes the string recieved back and parses it into an object
        let data = await response.json();

        return data;

    // if something goes wrong, show error alert
    } catch (error) {
        swal.fire({
            icon: 'error',
            backdrop: false,
            title: 'Oops!',
            text: 'Something went wrong reaching the TMBD API.'
        })
    }
}

// Shows all the movie cards on the page
function displayMovies(movies) {

    // Get the div that the movie cards will be placed into
    const movieListDiv = document.getElementById('movie-list');

    // Initialize the DIV, make sure it's empty
    movieListDiv.textContent = '';

    // Get the template for the movie cards
    const moviePosterTemplate = document.getElementById('movie-card-template');

    // Create the movie card from the template for each movie returned in the list
    for (let i = 0; i < movies.length; i++) {

        let movie = movies[i];

        // Copy everything in template tags
        let movieCard = moviePosterTemplate.content.cloneNode(true);

        // Uses a CSS selector to get the element and reassign the value of the img src attribute
        let movieImgElement = movieCard.querySelector('.card-img-top');
        movieImgElement.src = `https://image.tmdb.org/t/p/w500${movie.poster_path}`

        // Get the element holding the movie title and replace the text with the title of the movie
        let movieTitleElement = movieCard.querySelector('.card-body > h5');
        movieTitleElement.textContent = movie.title;

        // Get the element holding the paragraph information and replace the text with the summary of movie
        let movieParagraphElement = movieCard.querySelector('.card-text');
        movieParagraphElement.textContent = movie.overview;

        // Make up own attribute to assign movie id to access this information later for the modal
        let movieButton = movieCard.querySelector('.btn-primary');
        movieButton.setAttribute('data-movieId', movie.id);

        // Add the movie card to the page
        movieListDiv.appendChild(movieCard);
    }

}

// Get the list of movies with no filter and put them on the page
async function displayAllMovies() {

    let movies = await getMovies();
    displayMovies(movies);

}

// Put the movie details in the modal when "More Info" button is pressed
async function showMovieDetails(clickedBtn) {

    // get the ID of the movie that was clicked
    let movieId = clickedBtn.getAttribute('data-movieId');

    // get the details of the movie with that ID from TMBD API
    let movieData = await getMovie(movieId);

    // Modify Img on Modal
   let movieImg = document.querySelector('#movieModal .movie-image');
   movieImg.src = `https://image.tmdb.org/t/p/w500${movieData.poster_path}`

   // Modify Title on Modal
   let modalTitle = document.querySelector('#movieModal .movie-title');
   modalTitle.textContent = movieData.title;

   // Modify Tagline on Modal
   let modalTagline = document.querySelector('#movieModal .tagline');
   modalTagline.textContent = `${movieData.tagline}`;

   // Display genres on page
   displayGenres(movieData.genres);

   // Modify overview summary of Modal
   let modalBody = document.querySelector('#movieModal .overview');
   modalBody.textContent = movieData.overview;

   // Modify homepage website button links to on Modal
   let moviePageBtn = document.querySelector('#movieModal .btn-primary');
   moviePageBtn.href = movieData.homepage;

   // Modify Release Date
   let releaseDateMonth = document.getElementById('month');
   releaseDateMonth.textContent = movieData.release_date.slice(5,7);

   let releaseDateDay = document.getElementById('day');
   releaseDateDay.textContent = movieData.release_date.slice(8,10);

   let releaseDateYear = document.getElementById('year');
   releaseDateYear.textContent = movieData.release_date.slice(0,4);

   // Modify Runtime
   let runtime = document.getElementById('runtime');
   runtime.textContent = movieData.runtime;

   // Modify Voter average
   let voteAvg = document.getElementById('voteAvg');
   voteAvg.textContent = (movieData.vote_average).toFixed(2);

   // Modify Voter count
   let voteCount = document.getElementById('voteCount');
   voteCount.textContent = movieData.vote_count;
}

// Add genre badges to genre div on page with all Genres associated with movie
function displayGenres(movieGenreArray) {

    // Get div element to hold genre badges
    const genreDiv = document.getElementById('genres');
    genreDiv.textContent = '';

    // loop through each item in array and add genre element to div element
    for (let i = 0; i < movieGenreArray.length; i++) {

        let badge = document.createElement('span');
        badge.classList.add('badge','text-bg-success','mx-1');
        badge.textContent = movieGenreArray[i].name;
        genreDiv.appendChild(badge);
    }
}

// Filter the movies displayed when the particular genre button is clicked
async function filterByGenre(filterBtn) {

    // Get the genre ID of the btn filter that was clicked
    let genreId = parseInt(filterBtn.getAttribute('data-genreId'));

    // Grab the movies Array from the database
    let movies = await getMovies();

    // Declare new array to put the filtered movies into
    let filteredMovies = [];

    // For movies that equal the genre id, put it into new movie array
    for (let i = 0; i < movies.length; i++) {

        // Get genres for movie
        let movieGenreIds = movies[i].genre_ids;

        // Add movie to new array if it has genre id
        if ( movieGenreIds.includes(genreId) ) {
            filteredMovies.push(movies[i]);
        }
    }

    // Display movies based on new filtered array
    displayMovies(filteredMovies);
}
JavaScript

TL;DR

In order to grab data with a 3rd-party API, use the fetch method in a try...catch statement so that the app can gracefully inform the user of an issue if there is one trying to access the data. This needs to go inside an async function with the await keyword proceeding fetch since it takes awhile (comparatively) to grab the data from the API and return it.

Code Explanation

Movie Garden was created with the following functions.

getMovies grabs data about the most popular movies from "The Movide DB" (TMDB) using their API with the fetch method in a try...catch statement. With the try...catch statement the app can gracefully inform the user of an issue if there is one trying to access the data instead of just breaking. In this case it'll show a sweet alert. This needs to go inside an async function with the await keyword proceeding fetch since it takes awhile (comparatively) to grab the data from the API and return it.

getMovie is similar to getMovies except instead of using the API to get data for the most popular movies, it uses the API to get detailed data for one movie. The one movie is identified with the movieId which is a parameter for this function and used in the API call using string interpolation.

displayMovies shows the movies that are passed in as a parameter on the page. This is done by first grabbing the HTML element the cards are to be placed in. A template tag is used on the elements of the card so the same structure can be used for each of the cards. This is used in conjunction with a for loop to modify each element of the card template for each movie and add it to the element the cards are to be placed in. The most unique element that was modified is the button where a custom data-movieId attribute was set on each of the cards so that the Id of the movie could be accessed when the "More Info" button is clicked.

displayAllMovies uses getMovies to grab the popular movie data from TMBD and passes that data as an argument of displayMovies.

showMovieDetails puts the details of the movie into the modal when the "More Info" button is clicked. The button that was clicked is a parameter of this function. Using the custom data-movieId attribute on the button, movieId is passed as an argument to getMovie. The elements of the modal are then filled in the with data recieved back from the API call.

displayGenres takes in an array of genres for a specific movie as a parameter. It then gets the element from the modal that is to hold the genre badges to display all the genres associated with a particular movie.

filterByGenre takes in a clicked filter button as a parameter. From that button, the genreId is grabbed which is then used with the movies returned by getMovies. Using a for loop, each of the movies are checked to see if they have a genreId which includes the genreId of the filter button that was clicked. If it does, the movie is placed in a new array. This array is then passed as an argument to displayMovies so that only those movie cards are shown on the page.

What I learned

  • There are functions that take awhile to run. These require the await keyword to be placed before it. If an await keyword is required it needs to be inside an async function. The await keyword allows for the following function to finish doing its job before continuing with the rest of the async function. For example when used with fetch to get data from an API.
  • Use a try...catch statement to control what happens if something goes wrong when implementing resources from 3rd-party sources. For example using fetch to access data from an API about movies. If an error happens, the catch block can be coded to present the user with information about what happened and next possible actions with a great UI experience.
  • When trying to display similar elements on a page where only the content is changing, use HTML templates to easily recreate the elements and replace the content accordingly along with .content.cloneNode(true) on the template element in the DOM.

Improvements

  • Make sidebar sticky.
  • Add also "Now Playing", "Top Rated", "Upcoming", "Trending" movie lists.
  • A feature where a user can come to visit the page and their favorite movies can be saved to a list that is stored in the local storage.