The Code

                        
							// Get all list of movies
							async function getPopularMovies() {
								try {
									const popularMoviesUrl = 'https://api.themoviedb.org/3/movie/popular';
								
									// the easiest way to fetch an API (asynchronous)
									let response = await fetch(popularMoviesUrl, {
										headers: {
											Authorization: `Bearer ${API_KEY}`,
										},
									});

									if (response.ok) {
										let data = await response.json();
										return data.results;
									}
								
								} catch (error) {
									console.log(error)
									return [];
								}
							}

							// display all list of movies using our template
							function displayMovies(movies) {
								// get our movie card template
								const movieCardTemplate = document.getElementById('movie-card-template');
								// find the element where the movie card will go
								const movieRow = document.getElementById('movie-row');
								movieRow.innerHTML = ''; // when we click the button we do not need to add another list

								// for each movie in the movies array
								movies.forEach((movie) => {
									// - copy the template
									let movieCard = movieCardTemplate.content.cloneNode(true);
									// -
									let titleElement = movieCard.querySelector('.card-body > h5');
									titleElement.textContent = movie.title;

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

									let movieImgElement = movieCard.querySelector('.card-img-top');
									movieImgElement.setAttribute(
										'src',
										'https://image.tmdb.org/t/p/w500' + movie.poster_path
									);

									// let's set up the id for all movies
									let infoButton = movieCard.querySelector('.btn-primary');
									infoButton.setAttribute('data-movieId', movie.id);

									// let's set up the id for the favorite button
									let favoriteButton = movieCard.querySelector('.btn-outline-primary');
									favoriteButton.setAttribute('data-movieId', movie.id);

									movieRow.appendChild(movieCard);
								});
							}

							// get the movie by id (single responsibility principle)
							async function getMovieById(movieId) {
								try {
									const movieDetailUrl = `https://api.themoviedb.org/3/movie/${movieId}?language=en-US`;
									// const movieDetailUrl = 'https://api.themoviedb.org/3/movie/' + movieId
								
									let response = await fetch(movieDetailUrl, {
										headers: {
											Authorization: `Bearer ${API_KEY}`,
										},
									});

									if (response.ok) {
										let data = await response.json();
										return data;
									}
								
								} catch (error) {
									console.log(error);
									return undefined;
								}
							}

							// get the movie details
							async function getMovieDetails(infoBtn) {
								// access the movie id by getting the attribute we've set on the displayMovies function
								let movieId = infoBtn.getAttribute('data-movieId');

								let movie = await getMovieById(movieId);

								if (movie != undefined) {
									// return data object
									showMovieDetails(movie);
								}
							}

							// display the single movie
							function showMovieDetails(movie) {
								// format number to US dollar currency
								let USDollar = new Intl.NumberFormat('en-US', {
									style: 'currency',
									currency: 'USD',
								});

								// get the element where want to put the title, description, image
								let movieTitle = document.querySelector('.movieTitle');
								movieTitle.textContent = movie.title;
								let movieDescription = document.querySelector('.movieDescription');
								movieDescription.textContent = movie.overview;
								let moviePoster = document.querySelector('.movie-poster-details');
								moviePoster.setAttribute(
									'src',
									`https://image.tmdb.org/t/p/w500${movie.poster_path}`
								);
								let movieTagline = document.querySelector('.tagline');
								if (movie.tagline) {
									movieTagline.textContent = movie.tagline;
								} else {
									movieTagline.textContent = movie.title;
								}

								let movieBudget = document.querySelector('.budget');
								if (movie.budget) {
									movieBudget.textContent = USDollar.format(movie.budget);
								} else {
									movieBudget.textContent = 'No budget info available for now.';
								}

								let movieRevenue = document.querySelector('.revenue');
								if (movie.revenue) {
									movieRevenue.textContent = USDollar.format(movie.revenue);
								} else {
									movieRevenue.textContent = 'No revenue info available for now.';
								}

								// release-date
								let releaseDate = document.querySelector('.release-date');
								releaseDate.textContent = convertDate(movie.release_date);

								// Accessing attributes inside the 'genres' attribute
								const nameAttributes = movie.genres.map((item) => item.name);
								const genresName = nameAttributes.join(', ');
								let movieGenres = document.querySelector('.movie-genres');
								movieGenres.textContent = genresName;

								// getting the img with the class productionCompanies
								let companiesLogo = document.querySelector('.productionCompanies');
								companiesLogo.innerHTML = '';
								// Accessing the production_companies
								let productionCompanies = movie.production_companies.map(
									(item) => item.logo_path
								);

								// getting each logo and create a new image
								productionCompanies.forEach((logo) => {
									// check if the logo exist
									if (logo) {
										// create a new image element
										let newImage = document.createElement('img');
										newImage.setAttribute(
											'src',
											`https://image.tmdb.org/t/p/w500${logo}`
										);
										newImage.setAttribute('class', 'w-25 pe-2');
										newImage.setAttribute('alt', 'Company logo');
										// add the new image to the screen
										companiesLogo.appendChild(newImage);
									}
								});
							}

							// convert the date format
							function convertDate(inputDate) {
								const dateObject = new Date(inputDate);

								// Define options for formatting
								const options = {
									weekday: 'short',
									month: 'short',
									day: 'numeric',
									year: 'numeric',
								};

								// Use toLocaleDateString with the defined options
								const formattedDate = dateObject.toLocaleDateString('en-US', options);

								return formattedDate;
							}

							// display popular movies from the API
							async function displayPopularMovies() {
								let movies = await getPopularMovies();
								displayMovies(movies);
							}

							// display the movies from the localstorge
							function displayFavoriteMovies() {
								// get movies from getFavoriteMovies
								let movies = getFavoriteMovies();
								// display them (using displayMovies)
								displayMovies(movies);
							}

							// using local storage to save our favorites
							function saveFavoriteMovies(movies) {
								// serialization of the data
								// turn and array of objects into string
								let moviesAsString = JSON.stringify(movies);
								// save a complete list of our favorite movies
								localStorage.setItem('fyard-favorite-movies', moviesAsString);
							}

							// get our favorite movies from the local storage
							function getFavoriteMovies() {
								// retrive our list of favorite movies
								// return the list of movies
								let favoriteMovies = localStorage.getItem('fyard-favorite-movies');
								// null if we don't have not saved any movies yet
								if (favoriteMovies == null) {
									favoriteMovies = [];
									saveFavoriteMovies(favoriteMovies);
								} else {
									// deserialization of the data
									favoriteMovies = JSON.parse(favoriteMovies);
								}

								return favoriteMovies;
							}

							// Adding Favorite movies
							async function addFavoriteMovies(btn) {
								// save one new movie to our list of favorites
								let movieId = btn.getAttribute('data-movieId');

								// get my favorite movies
								let favorites = getFavoriteMovies();

								let duplicateMovieId = favorites.find((movie) => movie.id == movieId);

								// do not duplicate movies
								if (duplicateMovieId == undefined) {
									// get the whole movie
									let newFavorite = await getMovieById(movieId);

									if (newFavorite != undefined) {
										// add the new movie to the array
										favorites.push(newFavorite);
										// save the new array of movies
										saveFavoriteMovies(favorites);
									}
								}
							}

							// remove a favorite movie
							function removeFavoriteMovies(btn) {
								// remove one movie from our list of favorites
								let movieId = btn.getAttribute('data-movieId');
								// remove the movie from our list in local storage
								let favorites = getFavoriteMovies();
								favorites = favorites.filter(movie => movie.id != movieId);
								saveFavoriteMovies(favorites);
								// display the new list of favorites
								displayFavoriteMovies()
							}							
                        
                    
API (Application Programming Interface)

Fetching APIs is a fundamental aspect of modern software development that enables applications to communicate, share data, and leverage external services, leading to more feature-rich, efficient, and interconnected software systems. APIs define the methods and data formats that applications can use to request and exchange information.

In this project we are goint to use TMDB to retrieve data from their remote servers. In our case we need to get the popular movies and more.

Let's get to it!
getPopularMovies()

The getPopularMovies function is responsible for communicating with the TMDB remote server for fetching a list of popular movies. In the server documentation we found the link that allows us to retrieve the data.

The fetch function is used to make an asynchronous request to the specified API endpoint (popularMoviesUrl). The await keyword is used to wait for the asynchronous operation to complete, and the result is stored in the response variable. Next, we include an Authorization header in the API request, and the value of the Authorization header is set to a Bearer token, which dynamically generated using an API_KEY variable that i got from my TMDB account. After that, we tcheck if the HTTP response status is OK (in the range 200-299). If the response is successful, the code proceeds to handle the data. Than we parse the response body into a json format, and the array of popular movies extracted from the API response. At the end we catch any errors that might occur during the execution of the try block. If an error occurs, the function returns an empty array.

So our try, catch block handles potential errors, and returns either the array of popular movies or an empty array in case of an error. And we design the fonction to be used in an asynchronous context.

displayMovies()

To display the movie on the page we have created a <template> tag that contain a card to show each movie. This function is responsible to access the template tag element by it's id, access the row where we want to insert the result, and clear the row if the list od movie already exist. The display function take an array (a collection of movie object), all we have to do now is to iterate through the collection, create a copy of the template element, and modify the information container such as the title, the overvie, the movie poster and so one. We have also our card button, and create a new attribute for this button for future use by using setAttribute. Finally we add each movie card into our movie row using appenChild.

getMovieById()

The getMovieById function allows to fetch data from the TMDB remote server to access each movie separately using their id.

Fisrt, we use async to Indicates that the function is asynchronous. In the fonction wi need a parameter that represent the the unique identifier of the movie. We are using a try block that contains the code that may potentially throw an exception. Inside the try block we constructs the URL for the TMDb API endpoint to fetch details of a specific movie based on the provided movieId. Inside the response variable we use the fetch function to send a GET request to the constructed API endpoint that includes an Authorization header with an API key (API_KEY) for authentication. Note that the await pauses the execution of the function until the promise returned by fetch is resolved. Next we are tchecking if the response status is OK (status code 200). If true, it means the request was successful. We use await to pause again while parsing the JSON data from the response body. Finally, we use the catch block for cases where an error occurs within the try block. The error is logged to the console for debugging purposes, and the function returns undefined in case of an error.

getMovieDetails()

The getMovieDetails is also behave asynchronously, and take a parameter representing the button element that triggered the function, expecting the button to have a data-movieId attribute containing the movie's unique identifier, this is why we have set the data-movieId attribute while displaying the data. Now all we have to do is to get this data that we have set, so we can retrieve the movieId and store it into a variable with the same name movieId. Now we can call our getMovieById function and passing as argument the retrieved movieId. As you know we are using the await that pauses the execution of the function until the promise returned by getMovieById is resolved, and store the resolved value (the movie details) into a movie variable. If the movie variable is not equal to undefined, the condition ensures that the movie details were successfully retrieved, and calls the showMovieDetails function with the movie details as an argument. Note that if the movie details were not successfully retrieved (i.e., movie is undefined), no further action is taken.

showMovieDetails()

The showMovieDetails function dynamically updates various HTML elements on the web page to present detailed information about a movie, including its title, description, poster, tagline, budget, revenue, release date, genres, and production companies with their logos.

The first part just create an Intl.NumberFormat object named USDollar to format numbers as US dollars. Then we start by updating Movie Title, Description, and Poster doing the following steps:

  • Retrieve the HTML elements using document.querySelector for the movie's title, description, and poster.
  • Set the text content of the title and description elements with information from the provided movie object.
  • Set the src attribute of the poster image element with the URL constructed from the movie's poster path.
  • For the release date, we call the convertDate function defined bellow to convert the release date into a desired format.
  • Maps through the genres attribute of the movie object to extract genre names and joins them into a comma-separated string. Sets the text content of the genres element with this string.
  • Note that for each logo path, we create a new img element.
convertDate()

The convertDate function takes a date string (inputDate) as a parameter, converts it to a JavaScript Date object, and then formats it using the toLocaleDateString method with specific options. Finally, it returns the formatted date as a string. This function helpl converting date comming from the remote server we want to use in the movie details in a specific format. Note that the following line of code: const options = { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric' }; defines an options object with properties specifying the desired format for the date. As example, the input date '2024-02-01' is converted and formatted according to the specified options, resulting in the output 'Thu Feb 01 2024'.

displayPopularMovies()

The displayPopularMovies function is designed to display popular movies with the help of getPopularMovies that fetching the list of popular movies and store it in the movies variable. Finally, the displayMovies function is then called with the list of popular movies as an argument. This function is responsible for rendering the movies.

displayFavoriteMovies()

The displayFavoriteMovies function is designed to display the favorite movies with the help of getFavoriteMovies that retrieve the list of favorite movies and store it in the movies variable. Finally, the displayMovies function is then called with the list of favorite movies as an argument. This function is responsible for rendering the movies.

As you can see, The displayFavoriteMovies and displayPopularMovies follow a similar pattern. The key difference is the source of movies, the displayPopularMovies likely retrieved from an external source, and the displayFavoriteMovies displays favorite movies stored locally.

Talking about LocalStorage

Before going any further, let's talk a little about localstorage. What is LocalStorage?
LocalStorage is a web storage feature provided by web browsers to allow websites to store data persistently on a user's device. It provides a simple key-value storage mechanism and is a part of the Web Storage API along with sessionStorage. Note that both the key and the value are strings. If you need to store non-string data types, you'll need to convert them to strings before storing and parse them back when retrieving. The key distinction between localStorage and sessionStorage is that data stored in localStorage persists across browser sessions, while data in sessionStorage is only available for the duration of a single page session. The Data stored in localStorage persists even after the user closes and reopens the browser. It remains available until explicitly cleared by the user or the website.

saveFavoriteMovies()

The saveFavoriteMovies function purpose is to save a list of favorite movies to the browser's local storage. Fisrt we need to serialize the data. Serialization is the process of converting a data structure into a format that can be easily stored or transmitted, in this case, converting the array of objects into a JSON string. The input parameter movies, that is an array of objects representing favorite movies, is serialized into a string using JSON.stringify(), and stored into the moviesAsString variable. Now the serialized string (moviesAsString) is then saved to the local storage using localStorage.setItem(), and the key used for storing this data is set to 'fyard-favorite-movies'.

getFavoriteMovies()

The getFavoriteMovies function purpose is to retrieving the favorite movies from the local storage. The following line: let favoriteMovies = localStorage.getItem('fyard-favorite-movies'); attempts to retrieve the list of favorite movies from the local storage using the key 'fyard-favorite-movies'. The result (favoriteMovies) is a string representation of the stored data. We use the if statement to check if the retrieved data is 'null', indicating that no favorite movies have been saved yet. In this case, an empty array (favoriteMovies = []) is assigned to 'favoriteMovies', and the function saveFavoriteMovies(favoriteMovies) is called to save this empty array to local storage. This initializes the storage with an empty list of favorite movies. In case where the retrieved data is not 'null' (i.e., there are saved movies), the else block is executed, and the line favoriteMovies = JSON.parse(favoriteMovies); deserializes the string representation of the saved data (favoriteMovies) into a JavaScript array. Note that the JSON.parse function is used to convert the JSON string back into its original array form.
Finally, the function returns the list of favorite movies (favoriteMovies), which has either been retrieved from local storage or initialized as an empty array if no movies were previously saved.

addFavoriteMovies()

The addFavoriteMovies function Will add a new movie to our localstorage list. Moreover, to do so, we need to pass a button (btn) as a parameter that representing a button that triggers the addition of a movie to the list of favorites. Then it retrieves the movieId from the button's data attribute that was set as: ('data-movieId'). Next we need to get our favorite movies from the local storage by calling our getFavoriteMovies() function to retrieve the current list of favorite movies. We search for the movie we want to add into the list with the help of the built-in Javascript method (find), and store the result in the duplicateMovieId variable. After that, we check if there is a movie in the favorites list with the same id as the one being added in the duplicateMovieId because we do not want to have duplicate movie into our localstorage list. If there is actually no movie matching this id, we proceed to add the movie. So, if the movie is not a duplicate, we call the getMovieById(movieId) to get the details of the movie with the specified id. If the movie details are successfully retrieved (newFavorite != undefined), we just push it to the favorites array and save the updated list using our saveFavoriteMovies(favorites) function passing the new list of favorite movies.

removeFavoriteMovies()

The removeFavoriteMovies function is responsible for removing a favorite movie from the list. It takes a button (btn) as a parameter, and this parameter represent the button that will get triggered to remove a movie from the list of favorites. It retrieves the movieId from the button's data attribute, calls the getFavoriteMovies() function to retrieve the current list of favorite movies, and using the filter method to create a new array (favorites) excluding the movie with the specified id. After that, we call our saveFavoriteMovies() function to saves the updated list of favorite movies. Finally, we display the new updated list of favorite movies by calling the displayFavoriteMovies().

That's kind of cool, right? 😎