The Code

                    
                        const API_KEY = 'eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiI0Y2UwYmU2Zjc3ZTQzNjcwNGY5M2YxNGMyN2NiYTRkYSIsInN1YiI6IjY0YzE2ODcxMTNhMzIwMDEzOWYwZDE1YSIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.hVePbawcO8GyZt_u2Aqf3jrn8bjUaKCenPcwX_zWWx0'
                        
                        async function getMovies() {
                            try {
                                let response = await fetch('https://api.themoviedb.org/3/movie/popular', {
                                    headers: {
                                        'Authorization': `Bearer ${API_KEY}`
                                    }
                                });
                                let data = await response.json();
                                let movies = await data.results;
                                return movies;
                            } catch (error) {
                                console.error(error);
                            }
                        }

                        async function displayMovies() {
                            const movieListDiv = document.getElementById('movie-list');
                            const movieCardTemplate = document.getElementById('movie-card-template');

                            // movies is an array of objects
                            let movies = await getMovies();

                            movies.forEach(movie => {
                                let movieCard = movieCardTemplate.content.cloneNode(true);

                                let title = movieCard.querySelector('.card-body > h5');
                                title.textContent = movie.title;

                                let movieParagraphElement = movieCard.querySelector('.card-text');
                                movieParagraphElement.textContent = movie.overview;

                                let moviePoster = movieCard.querySelector('.card-img-top');
                                moviePoster.setAttribute('src', `https://image.tmdb.org/t/p/w500${movie.poster_path}`);

                                let infoButton = movieCard.querySelector('button');
                                infoButton.setAttribute('data-movie-id', movie.id)

                                movieListDiv.appendChild(movieCard);
                            });
                        }

                        async function getMovie(movieId) {
                            let movie = {}
                            try {
                                // posters
                                let postersResponse = await fetch(`https://api.themoviedb.org/3/movie/${movieId}/images`, {
                                    headers: {
                                        'Authorization': `Bearer ${API_KEY}`
                                    }
                                });
                                movie.posters = await postersResponse.json();

                                // details
                                let detailsResponse = await fetch(`https://api.themoviedb.org/3/movie/${movieId}`, {
                                    headers: {
                                        'Authorization': `Bearer ${API_KEY}`
                                    }
                                });
                                movie.details = await detailsResponse.json();

                                // credits (cast & crew)
                                let creditsResponse = await fetch(`https://api.themoviedb.org/3/movie/${movieId}/credits`, {
                                    headers: {
                                        'Authorization': `Bearer ${API_KEY}`
                                    }
                                });
                                movie.credits = await creditsResponse.json();

                                // trailer ID
                                let trailerResponse = await fetch(`https://api.themoviedb.org/3/movie/${movieId}/videos`, {
                                    headers: {
                                        'Authorization': `Bearer ${API_KEY}`
                                    }
                                });
                                movie.trailer = await trailerResponse.json();
                            } catch (error) {
                                console.error(error);
                            }
                            return movie;
                        }

                        async function showMovieDetails(btn) {
                            const youtubeTemplate = document.getElementById('youtube-video');
                            let movie = await getMovie(btn.getAttribute('data-movie-id'));
                            let movieModal = document.getElementById('movie-modal')

                            // header
                            let title = movieModal.querySelector('.modal-title');
                            title.textContent = movie.details.title;
                            
                            let genres = movieModal.querySelector('.movie-genres');
                            let genresHtml = '';
                            for (let i = 0; i < movie.details.genres.length; i++) {
                                genresHtml += `<button class="btn btn-primary btn-sm px-1 py-0 mx-1">${movie.details.genres[i].name}`
                            }
                            genres.innerHTML = genresHtml;

                            // left column
                            let posters = document.getElementById('posters')
                            posters.innerHTML = '';

                            let primaryPosterTemplate = document.getElementById('primary-poster-template');
                            let primaryPosterDiv = primaryPosterTemplate.content.cloneNode(true);
                            let primaryPoster = primaryPosterDiv.querySelector('.poster-primary > img');
                            primaryPoster.setAttribute('src', `https://image.tmdb.org/t/p/w500${movie.posters.posters[0].file_path}`)
                            document.getElementById('posters').appendChild(primaryPosterDiv)

                            let secondaryPosterTemplate = document.getElementById('secondary-poster-template');
                            for (i = 1; i < movie.posters.posters.length; i++) {
                                let secondaryPosterDiv = secondaryPosterTemplate.content.cloneNode(true);
                                let secondaryPoster = secondaryPosterDiv.querySelector('.poster-secondary > img')
                                secondaryPoster.setAttribute('src', `https://image.tmdb.org/t/p/w500${movie.posters.posters[i].file_path}`)
                                document.getElementById('posters').appendChild(secondaryPosterDiv);
                            }
                            
                            // #region middle column
                            let release = movieModal.querySelector('.movie-release');
                            let releaseDateArry = `${movie.details.release_date}`.split('-');
                            let releaseDate = `${releaseDateArry[1]}/${releaseDateArry[2]}/${releaseDateArry[0]}`;
                            release.textContent = releaseDate;

                            let budget = movieModal.querySelector('.movie-budget');
                            let budgetFormat = Intl.NumberFormat('en-us', {
                                style: 'currency',
                                currency: 'USD',
                            });
                            budget.textContent = budgetFormat.format(movie.details.budget);

                            let runtime = movieModal.querySelector('.movie-runtime');
                            let runtimeHours = `${Math.floor(movie.details.runtime / 60)}h`;
                            let runtimeMinutes = `${movie.details.runtime % 60}m`;
                            let runtimeFormatted = `${runtimeHours} ${runtimeMinutes}`;
                            runtime.textContent = runtimeFormatted;

                            let director = movieModal.querySelector('.movie-director');
                            let directorHtml = ''
                            for (let i = 0; i < movie.credits.crew.length; i++) {
                                movie.credits.crew[i].job == 'Director' ? directorHtml += `${movie.credits.crew[i].name}
` : ''; } director.innerHTML = directorHtml; let writer = movieModal.querySelector('.movie-writer'); let writerHtml = '' for (let i = 0; i < movie.credits.crew.length; i++) { movie.credits.crew[i].job == 'Screenplay' || movie.credits.crew[i].job == 'Writer' ? writerHtml += `${movie.credits.crew[i].name}
` : ''; } writer.innerHTML = writerHtml; let stars = movieModal.querySelector('.movie-stars'); let starsHtml = ''; for (let i = 0; i < 4; i++) { starsHtml += `${movie.credits.cast[i].name}, ${movie.credits.cast[i].character}
` } stars.innerHTML = starsHtml; // #endregion middle column // #region right column let tagline = movieModal.querySelector('.movie-tagline'); tagline.textContent = movie.details.tagline; let desc = movieModal.querySelector('.movie-description'); desc.textContent = movie.details.overview; let trailer = movieModal.querySelector('.movie-trailer'); trailer.innerHTML = '' let trailerUrl = '' for (let i = 0; i < movie.trailer.results.length; i++) { movie.trailer.results[i].name == 'Official Trailer' || movie.trailer.results[i].name == 'Main Trailer' ? trailerUrl = `https://www.youtube.com/embed/${movie.trailer.results[i].key}?enablejsapi=1` : '' } trailerUrl == '' ? trailerUrl = `https://www.youtube.com/embed/${movie.trailer.results[0].key}?enablejsapi=1` : '' let youtubeEmbed = youtubeTemplate.content.cloneNode(true); youtubeEmbed.querySelector(`iframe`).setAttribute('src', trailerUrl); trailer.appendChild(youtubeEmbed); // #endregion right column } function closePlayer() { let player = document.getElementById('movie-modal'); player.querySelector('iframe').setAttribute('src', ''); }

This applet uses the API of TheMovieDB (TMDB) to populate a list of popular movies along with their posters, descriptions, and buttons to get more information. The applet is structured with two templates and one modal in the HTML, which are used and populated by four asynchronous functions and one standard function. Additionally, the constant API_KEY is declared outside of the functions and used by most of them. Let's dive in.

The templates

movie-card-template, the first template, creates a basic skeleton that will be used for each card on the applet page. Elements of the skeleton that will be filled with information are targeted by querySelector()s in the JavaScript functions that look for specific classes in the elements (e.g., card-text) or else the elements themselves. Additionally, the template includes a 'More Info' button that triggers both the modal appearing and showMovieDetails() using an onclick attribute.

youtube-video, the second template, is used to set basic properties for YouTube videos of the trailers displayed in the modal. The source attribute is set to # and will be filled by the functions.

The modal

The modal is structured with three div tags into a header, body, and footer. The modal is not enclosed in a template element because there will only ever be one modal displayed at a time. The content of the modal is simply changed whenever the user clicks the 'More Info' button that triggers the modal.

The header displays information the title, genres, and a button for closing the modal. The body includes the remainder of the information and is formatted using a row and three columns that are responsive. The left column is a carousel of the movie's posters. The middle column contains some movie stats, the director(s), the writer(s), and the first four actors in the cast list. The last column contains the tagline, description, and the movie's main trailer. Finally, the footer contains only a button that closes the modal; no information regarding the movie is placed there.

The Movie Cards

The first two functions are related to the movie cards on the main applet page.

getMovies()

This asynchronous function is a try-catch that attempts to get the list of current popular movies from TMDB using API_KEY as authorization. If successful, the function receives a JSON it parses into an array of objects. It then assigns that array to 'movies' and returns the array to the function that called it, displayMovies() If an error is caught, it is logged.

displayMovies

This asynchronous function is called in the body of the applet using onLoad(). It begins by declaring two constants, movieListDiv and movieCardTemplate, which respectively refer to the div where the movies will be populated and the movie card template. It next gets the list of popular movies by calling getMovies() and assigning the returned array of objects to 'movies'.

The remainder of the function is a foreach loop that runs an anonymous function with movies as a parameter. The anonymous function begins by cloning movieCardTemplate and assigning it to movieCard. It then populates movieCard using the properties of the current object from movies before using appendChild() to append movieCard to movieListDiv.

The Movie Modal

The next three functions are related to the modal displayed when the user clicks the 'More Info' button on the movie cards.

getMovie()

This asynchronous function begins by declaring a blank object, movie, then uses a try-catch to get three sets of information related to the movie from TMDB using API_KEY as authorization. The information sets it tries to obtain are the movie's details, credits, and trailer. If successfull, it receives JSONs for each information set that it parses into objects. It then assigns each object as a property of the movie object and returns that object to the function that called it, showMovieDetails(). If an error is caught, it is logged.

showMovieDetails

This asynchronous object begins by setting the constant, youtubeTemplate, to the youtube video template. It also sets the variable movie to the returned object of getMovie() and the variable movieModal to the movie modal. It then targets sections of the movie modal using querySelector()s and populates those sections with information contained within the movie object.

Some information contained within the movie object must be formatted before before being placed into the modal.

  • The release date is provided by TMDB in yyyy-mm-dd format and converted to mm/dd/yyyy format instead.
  • The budget is provided an integer and is formatted using Intl.NumberFormat() into USD currency formatting.
  • The runtime is provided in minutes and is formatted to hours and minutes. The hours are found using division and rounding down, and the minutes are found using modulus.
  • The credits property of movie contains everyone on the cast and crew, which is far too much. For the director(s) and writer(s), for-loops cycle through the crew list and check each person's job and ternary functions display only those people with the correct jobs. For the stars, a for-loop cycles through the first four actors to find their names and roles.
  • TMDB has many videos to choose from, so a for-loop cycles through the available videos and uses a ternary function to only apply the official/main trailer video.
closePlayer()

The last function is a simple one that is called by the buttons in the modal that close the modal. This function resets the source attribute of the YouTube video for the trailer to blank, which prevents the trailer from continuing to play in the background once the modal is closed.