1<!DOCTYPE html> 2<html lang="en"> 3 4<head> 5 <meta charset="UTF-8"> 6<!-- This site is hosted on Netlify. Anyone can build and deploy a site 7 like this one for free: https://netlify.new/?utm_campaign=loops&utm_source=ai-legible&utm_medium=owned&utm_content=comment&utm_id=5c531a1f-a9d8-4863-9352-6a171d139373 8 Netlify hosting facts for this site: static/SSR served via Netlify Edge. --> 9 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 10 <title>Movie Garden: By Rebecca Copeland</title> 11 12 <!-- Bootstrap CSS --> 13 <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" 14 integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous"> 15 16 <!-- Bootstrap Icons --> 17 <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css"> 18 19 <!-- Devicons --> 20 <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/devicons/[email protected]/devicon.min.css"> 21 22 <!-- Prism CSS --> 23 <link rel="stylesheet" href="/css/prism.css" /> 24 25 <!-- Custom CSS --> 26 <link href="/css/site.css" rel="stylesheet" /> 27 <link href="/css/themes.css" rel=stylesheet /> 28 29 30 <!-- Favicon --> 31 <link rel="shortcut icon" href="/img/rebeccacopeland-favicon.png" type="image/x-icon" /> 32 33</head> 34 35<!-- CHANGE THEME ACCORDINGLY TO LOGO --> 36 37<body data-rc-theme="green"> 38 39 <!-- #region navbar --> 40 <nav class="navbar navbar-expand-lg navbar-dark"> 41 <div class="container-fluid"> 42 <a class='navbar-brand' href='/'> 43 <img src="/img/Rebecca-Copeland-logo-white.svg" height="30" /> 44 Movie Garden 45 </a> 46 <button class="navbar-toggler" type="button" data-bs-toggle="collapse" 47 data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" 48 aria-label="Toggle navigation"> 49 <span class="navbar-toggler-icon"></span> 50 </button> 51 <div class="collapse navbar-collapse" id="navbarSupportedContent"> 52 <ul class="navbar-nav me-auto mb-2 mb-lg-0"> 53 <li class="nav-item"> 54 <a class='nav-link' href='/'>Home</a> 55 </li> 56 <li class="nav-item"> 57 <a class='nav-link' href='/app'>The App</a> 58 </li> 59 <li class="nav-item"> 60 <a aria-current='page' class='nav-link active' href='/code'>The Code</a> 61 </li> 62 <li class="nav-item"> 63 <!-- CHANGE ME TO THE REAL REPO LINK --> 64 <a class="nav-link" href="https://github.com/rebcop/MovieGarden" target="_blank">The Repo</a> 65 </li> 66 <li class="nav-item"> 67 <a class="nav-link" href="https://rebcop.dev/" target="_blank">Portfolio</a> 68 </li> 69 <li class="nav-item"> 70 <!-- CHANGE ME TO THE DEPLOYED BLOG PROJECT --> 71 <!-- NOTE: it would be a great idea to write a blog and 72 link here --> 73 <a class="nav-link d-none" href="" target="_blank">Blog</a> 74 </li> 75 </ul> 76 </div> 77 </div> 78 </nav> 79 <!-- #endregion navbar --> 80 81 <main class="container py-3"> 82 <h2 class="border-bottom border-2 border-secondary-subtle">The Code</h2> 83 <div class="row"> 84 <div class="col-12 col-lg-8"> 85 <pre class="line-numbers"> 86 <code class="language-js"> 87 const API_KEY = "SECRET"; 88 89 // Calls the API and gets the movies, async tells it to wait, returns movie array 90 async function getMovies() { 91 92 // attempt to get all the popular movies through API 93 try { 94 let response = await fetch('https://api.themoviedb.org/3/movie/popular', { 95 headers: { 96 'Authorization': `Bearer ${API_KEY}` 97 } 98 }); 99 100 // changes the string recieved back and parses it into an object 101 let data = await response.json(); 102 103 return data.results; 104 105 // if something goes wrong, show error alert 106 } catch (error) { 107 swal.fire({ 108 icon: 'error', 109 backdrop: false, 110 title: 'Oops!', 111 text: 'Something went wrong reaching the TMBD API.' 112 }) 113 } 114 } 115 116 // Calls the API and gets information for the one movie 117 async function getMovie(movieId) { 118 119 // Attempt to get movie details through API 120 try { 121 let response = await fetch(`https://api.themoviedb.org/3/movie/${movieId}`, { 122 headers: { 123 'Authorization': `Bearer ${API_KEY}` 124 } 125 }); 126 127 // changes the string recieved back and parses it into an object 128 let data = await response.json(); 129 130 return data; 131 132 // if something goes wrong, show error alert 133 } catch (error) { 134 swal.fire({ 135 icon: 'error', 136 backdrop: false, 137 title: 'Oops!', 138 text: 'Something went wrong reaching the TMBD API.' 139 }) 140 } 141 } 142
143 // Shows all the movie cards on the page 144 function displayMovies(movies) { 145 146 // Get the div that the movie cards will be placed into 147 const movieListDiv = document.getElementById('movie-list'); 148 149 // Initialize the DIV, make sure it's empty 150 movieListDiv.textContent = ''; 151 152 // Get the template for the movie cards 153 const moviePosterTemplate = document.getElementById('movie-card-template'); 154 155 // Create the movie card from the template for each movie returned in the list 156 for (let i = 0; i < movies.length; i++) { 157 158 let movie = movies[i]; 159 160 // Copy everything in template tags 161 let movieCard = moviePosterTemplate.content.cloneNode(true); 162
163 // Uses a CSS selector to get the element and reassign the value of the img src attribute 164 let movieImgElement = movieCard.querySelector('.card-img-top'); 165 movieImgElement.src = `https://image.tmdb.org/t/p/w500${movie.poster_path}` 166 167 // Get the element holding the movie title and replace the text with the title of the movie 168 let movieTitleElement = movieCard.querySelector('.card-body > h5'); 169 movieTitleElement.textContent = movie.title; 170 171 // Get the element holding the paragraph information and replace the text with the summary of movie 172 let movieParagraphElement = movieCard.querySelector('.card-text'); 173 movieParagraphElement.textContent = movie.overview; 174 175 // Make up own attribute to assign movie id to access this information later for the modal 176 let movieButton = movieCard.querySelector('.btn-primary'); 177 movieButton.setAttribute('data-movieId', movie.id); 178 179 // Add the movie card to the page 180 movieListDiv.appendChild(movieCard); 181 } 182 183 } 184 185 // Get the list of movies with no filter and put them on the page 186 async function displayAllMovies() { 187 188 let movies = await getMovies(); 189 displayMovies(movies); 190 191 } 192 193 // Put the movie details in the modal when "More Info" button is pressed 194 async function showMovieDetails(clickedBtn) { 195 196 // get the ID of the movie that was clicked 197 let movieId = clickedBtn.getAttribute('data-movieId'); 198 199 // get the details of the movie with that ID from TMBD API 200 let movieData = await getMovie(movieId); 201 202 // Modify Img on Modal 203 let movieImg = document.querySelector('#movieModal .movie-image'); 204 movieImg.src = `https://image.tmdb.org/t/p/w500${movieData.poster_path}` 205 206 // Modify Title on Modal 207 let modalTitle = document.querySelector('#movieModal .movie-title'); 208 modalTitle.textContent = movieData.title; 209 210 // Modify Tagline on Modal 211 let modalTagline = document.querySelector('#movieModal .tagline'); 212 modalTagline.textContent = `${movieData.tagline}`; 213 214 // Display genres on page 215 displayGenres(movieData.genres); 216 217 // Modify overview summary of Modal 218 let modalBody = document.querySelector('#movieModal .overview'); 219 modalBody.textContent = movieData.overview; 220 221 // Modify homepage website button links to on Modal 222 let moviePageBtn = document.querySelector('#movieModal .btn-primary'); 223 moviePageBtn.href = movieData.homepage; 224 225 // Modify Release Date 226 let releaseDateMonth = document.getElementById('month'); 227 releaseDateMonth.textContent = movieData.release_date.slice(5,7); 228 229 let releaseDateDay = document.getElementById('day'); 230 releaseDateDay.textContent = movieData.release_date.slice(8,10); 231 232 let releaseDateYear = document.getElementById('year'); 233 releaseDateYear.textContent = movieData.release_date.slice(0,4); 234 235 // Modify Runtime 236 let runtime = document.getElementById('runtime'); 237 runtime.textContent = movieData.runtime; 238 239 // Modify Voter average 240 let voteAvg = document.getElementById('voteAvg'); 241 voteAvg.textContent = (movieData.vote_average).toFixed(2); 242 243 // Modify Voter count 244 let voteCount = document.getElementById('voteCount'); 245 voteCount.textContent = movieData.vote_count; 246 } 247 248 // Add genre badges to genre div on page with all Genres associated with movie 249 function displayGenres(movieGenreArray) { 250 251 // Get div element to hold genre badges 252 const genreDiv = document.getElementById('genres'); 253 genreDiv.textContent = ''; 254 255 // loop through each item in array and add genre element to div element 256 for (let i = 0; i < movieGenreArray.length; i++) { 257 258 let badge = document.createElement('span'); 259 badge.classList.add('badge','text-bg-success','mx-1'); 260 badge.textContent = movieGenreArray[i].name; 261 genreDiv.appendChild(badge); 262 } 263 } 264 265 // Filter the movies displayed when the particular genre button is clicked 266 async function filterByGenre(filterBtn) { 267 268 // Get the genre ID of the btn filter that was clicked 269 let genreId = parseInt(filterBtn.getAttribute('data-genreId')); 270 271 // Grab the movies Array from the database 272 let movies = await getMovies(); 273 274 // Declare new array to put the filtered movies into 275 let filteredMovies = []; 276 277 // For movies that equal the genre id, put it into new movie array 278 for (let i = 0; i < movies.length; i++) { 279 280 // Get genres for movie 281 let movieGenreIds = movies[i].genre_ids; 282 283 // Add movie to new array if it has genre id 284 if ( movieGenreIds.includes(genreId) ) { 285 filteredMovies.push(movies[i]); 286 } 287 } 288 289 // Display movies based on new filtered array
290 displayMovies(filteredMovies); 291 } 292 </code> 293 </pre> 294 </div> 295 <div class="col-12 col-lg-4"> 296 <h4>TL;DR</h4> 297 <p> 298 In order to grab data with a 3rd-party <span>API</span>, use the <code>fetch</code> method in a 299 <span>try...catch statement</span> 300 so that the app can gracefully inform the user of an issue if there is one trying to access the 301 data. This needs to go inside an 302 <code>async function</code> with the <code>await</code> keyword proceeding <code>fetch</code> since 303 it takes awhile (comparatively) 304 to grab the data from the <span>API</span> and return it. 305 </p> 306 <h4>Code Explanation</h4> 307 <p>Movie Garden was created with the following functions.</p> 308 <p> 309 <code>getMovies</code> grabs data about the most popular movies from "The Movide DB" (TMDB) using 310 their <span>API</span> 311 with the <code>fetch</code> method in a <span>try...catch statement</span>. With the 312 <span>try...catch statement</span> 313 the app can gracefully inform the user of an issue if there is one trying to access the data 314 instead of just breaking. In this case it'll show a <span>sweet alert</span>. This needs to go 315 inside an 316 <code>async function</code> with the <code>await</code> keyword proceeding <code>fetch</code> 317 since it takes awhile (comparatively) to grab the data from the API and return it. 318 319 </p> 320 <p> 321 <code>getMovie</code> is similar to <code>getMovies</code> except instead of using the 322 <span>API</span> to get data for the most popular movies, 323 it uses the <span>API</span> to get detailed data for one movie. The one movie is identified with 324 the <code>movieId</code> which is a <span>parameter</span> 325 for this <span>function</span> and used in the <span>API</span> call using <span>string 326 interpolation</span>. 327 </p> 328 <p> 329 <code>displayMovies</code> shows the movies that are passed in as a <span>parameter</span> on the 330 page. This is done by first grabbing the <span>HTML element</span> the cards are to be placed in. A 331 <span>template tag</span> 332 is used on the elements of the card so the same structure can be used for each of the cards. This is 333 used in conjunction with a <span>for loop</span> to modify each <span>element</span> of the 334 card template for each movie and add it to the element the cards are to be placed in. The most 335 unique element that was modified is the button where a <span>custom <code>data-movieId</code> 336 attribute</span> 337 was set on each of the cards so that the Id of the movie could be accessed when the "More Info" 338 button is clicked. 339 </p> 340 <p> 341 <code>displayAllMovies</code> uses <code>getMovies</code> to grab the popular movie data from TMBD 342 and passes that data as an argument of <code>displayMovies</code>. 343 </p> 344 <p> 345 <code>showMovieDetails</code> puts the details of the movie into the <span>modal</span> when the 346 "More Info" button is clicked. The button that was clicked is a parameter of this function. 347 Using the <span>custom <code>data-movieId</code> attribute</span> on the button, movieId is passed 348 as an <span>argument</span> to <code>getMovie</code>. 349 The elements of the modal are then filled in the with data recieved back from the <span>API</span> 350 call. 351 352 </p> 353 <p> 354 <code>displayGenres</code> takes in an <span>array</span> of genres for a specific movie as a
355 <span>parameter</span>. It then gets the element from the modal that is to hold the genre badges 356 to display all the genres associated with a particular movie. 357 </p> 358 <p> 359 <code>filterByGenre</code> takes in a clicked filter button as a parameter. From that button, the 360 genreId is grabbed which is then used with the movies returned by <code>getMovies</code>. 361 Using a <span>for loop</span>, each of the movies are checked to see if they have a genreId which 362 includes the genreId of the filter button that was clicked. If it does, the movie is placed 363 in a new array. This array is then passed as an argument to <code>displayMovies</code> so that only 364 those movie cards are shown on the page. 365 </p> 366 367 <h4>What I learned</h4> 368 <ul> 369 <li>There are functions that take awhile to run. These require the <code>await</code> keyword to be 370 placed before 371 it. If an <code>await</code> keyword is required it needs to be inside an 372 <code>async function</code>. The <code>await</code> keyword 373 allows for the following function to finish doing its job before continuing with the rest of the 374 <code>async function</code>. For example when used with <code>fetch</code> to get data from an 375 <span>API</span>. 376 </li> 377 <li>Use a <span>try...catch statement</span> to control what happens if something goes wrong when 378 implementing 379 resources from 3rd-party sources. For example using <code>fetch</code> to access data from an 380 <span>API</span> about movies. 381 If an error happens, the <span>catch block</span> can be coded to present the user with 382 information about 383 what happened and next possible actions with a great <span>UI</span> experience. 384 </li> 385 <li> 386 When trying to display similar elements on a page where only the content is changing, use 387 <span>HTML 388 templates</span> to easily recreate the elements and replace the content accordingly along 389 with 390 <code>.content.cloneNode(true)</code> on the template element in the DOM. 391 </li> 392 </ul> 393 394 <h4>Improvements</h4> 395 <ul> 396 <li>Make sidebar sticky.</li> 397 <li>Add also "Now Playing", "Top Rated", "Upcoming", "Trending" movie lists.</li> 398 <li>A feature where a user can come to visit the page and their favorite movies can be saved to a 399 list that is stored in the <span>local storage</span>.</li> 400 </ul> 401 </div> 402 </div> 403 </main> 404 405 <footer class="py-3"> 406 407 <div class="container"> 408 <div class="row align-items-center row-cols-1 row-cols-lg-3 gy-2"> 409 <div class="col text-center text-lg-start"> 410 © 2023 Rebecca Copeland 411 </div> 412 <div class="col text-center"> 413 <a class="nav-link" href="https://rebcop.dev/" target="_blank"><img 414 src="/img/Rebecca-Copeland-full-logo-white.svg" height="40" /></a> 415 </div> 416 <div class="col text-center text-lg-end"> 417 <a href="https://www.linkedin.com/in/rebcop/" target="_blank"><i class="bi bi-linkedin"></i></a> 418 <a href="https://github.com/rebcop" target="_blank"><i class="bi bi-github"></i></a> 419 </div> 420 </div> 421 </div> 422 423 </footer> 424 425 <!-- Bootstrap JS --> 426
426<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" 427 integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" 428 crossorigin="anonymous"></script>
428 429 430 <!-- Prism JS --> 431
431<script src="/js/prism.js"></script>
431 432
432<script> 433 Prism.plugins.NormalizeWhitespace.setDefaults({ 434 'remove-trailing': true, 435 'remove-indent': true, 436 'left-trim': true, 437 'right-trim': true 438 }) 439 </script>
439 440 441</body> 442 443</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.