1.2 DOM APIs Review

HTML5 App with Forms

# Star Wars Movie Example — Revisited

Its time to add some user interaction to our review example application. Augment your code from the previous exercise to allow the user to search for a specific movie title.

# Requirements

  • add a search form above the results list
  • no results should be displayed until the user submits the search
  • the user should be able to input a partial movie title to search for
  • if the input field is blank, it should return all of the movies
  • the user should be able to submit the form by pressing the enter key or clicking the search button
  • if the search returns no matching results, a message should be displayed for the user
  • if there is an error, display an alert style message above the Results
  • any alert messages should be cleared when new search results are displayed

# Bonus

Try displaying the results in Wookiee.

Starter Code
<!DOCTYPE html>
<html lang="en-CA">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>SWAPI Example</title>
    <link rel="stylesheet" href="style.css" />
  </head>

  <body>
    <div class="container">
      <h1>Star Wars Films</h1>

      <section id="search-results">
        <h2>Results</h2>
      </section>
    </div>

    <script src="./main.js"></script>
  </body>
</html>
'use strict'

getFilms()
  .then(filterResults)
  .then(displayList)

/**
 * Fetch an array of movie objects from the Star Wars API (swapi.dev)
 * @returns {Promise} Resolves to an Array of zero or more movie objects
 */
async function getFilms () {
  try {
    const response = await fetch('https://swapi.dev/api/films/')
    if (response.ok) {
      const jsonData = await response.json()
      return jsonData.results
      // Alternatively we could use object assignment destructuring
      // const { results } = await response.json()
      // return results
    }
    return []
  } catch (err) {
    // We should do more robust error handling here.
    console.error('Whoops, problem fetching data ...', err)
    return Promise.reject('Fetch failed')
  }
}

/**
 * Filter the raw results from the SWAPI and return a new array with only
 * the properties required for display: episodeId, title, releaseDate.
 *
 * @param {Object[]} films
 * @returns {Object[]}
 */
async function filterResults (films) {
  return films.map(film => {
    return {
      episodeId: film.episode_id,
      title: film.title,
      releaseDate: film.release_date
    }
  })
}

/**
 * Updates the UI with the given array of films.
 *
 * @param {Object[]} films
 * @param {number} films[].episodeId
 * @param {string} films[].title
 * @param {string} films[].releaseDate
 *
 * @returns {void}
 */
function displayList (films) {
  // This function is responsible for displaying the films,
  // so it is here that we should sort the returned data.
  films.sort((a, b) => a.episodeId - b.episodeId)

  const target = document.getElementById('movie-list')
  const ul = document.createElement('ul')

  films.forEach(film => {
    const li = document.createElement('li')
    li.innerHTML = `
        Episode ${film.episodeId}: 
        <strong>${film.title}</strong> 
        <em> (released ${film.releaseDate})</em>`
    ul.appendChild(li)
  })

  target.appendChild(ul)
}
Solution - main.js
'use strict'
const titleEl = document.getElementById('movie-title')

document.getElementById('search-form').addEventListener('submit', handleSubmit)

/**
 * Handle the `submit` event for the the `#search-form`.
 * @param {Object} event
 */
function handleSubmit (event) {
  event.preventDefault()
  getFilmsByTitle(titleEl.value)
    .then(filterResults)
    .then(displayList)
    .catch(displayAlert)
}

/**
 * Fetch an array of movie objects from the Star Wars API (swapi.dev)
 * @returns {Promise} Resolves to an Array of zero or more movie objects
 */
async function getFilmsByTitle (title) {
  // let format = '&format=wookiee'
  let format = ''
  try {
    const response = await fetch(
      'https://swapi.dev/api/films/?search=' + title + format
    )
    if (response.ok) {
      const { results } = await response.json()
      return results
    }
    return []
  } catch (err) {
    // We should do more robust error handling here.
    console.error('Whoops, problem fetching data ...', err)
    return Promise.reject('Fetch failed')
  }
}

/**
 * Filter the raw results from the SWAPI and return a new array with only
 * the properties required for display: episodeId, title, releaseDate.
 *
 * @param {Object[]} films
 * @returns {Object[]}
 */
async function filterResults (films) {
  return films.map(film => {
    return {
      episodeId: film.episode_id,
      title: film.title,
      releaseDate: film.release_date
    }
  })
}

/**
 * Updates the UI with the given array of films.
 *
 * @param {Object[]} films
 * @param {number} films[].episodeId
 * @param {string} films[].title
 * @param {string} films[].releaseDate
 *
 * @returns {void}
 */
function displayList (films) {
  clearAlert()

  if (films.length === 0) {
    displayAlert('Sorry, there were no matching results.', 'warning')
  }

  // This function is responsible for displaying the films,
  // so it is here that we should sort the returned data.
  films.sort((a, b) => a.episodeId - b.episodeId)

  const target = document.getElementById('search-results')

  let ul = document.getElementById('movie-list')
  if (ul) {
    ul.remove()
  }

  ul = document.createElement('ul')
  ul.id = 'movie-list'

  films.forEach(film => {
    const li = document.createElement('li')
    li.innerHTML = `
        Episode ${film.episodeId}: 
        <strong>${film.title}</strong> 
        <em> (released ${film.releaseDate})</em>`
    ul.appendChild(li)
  })

  target.appendChild(ul)
}

/**
 * Create an alert message box. Defaults to an error alert.
 * @param {string} message
 * @param {string} type
 */
function displayAlert (message, type = 'error') {
  const errorDiv = document.createElement('div')
  errorDiv.id = 'alert'
  errorDiv.className = 'alert ' + type
  errorDiv.textContent = message

  document.getElementById('search-results').prepend(errorDiv)
}

/**
 * Remove any previously displayed alert message
 */
function clearAlert () {
  const alert = document.getElementById('alert')
  if (alert) alert.remove()
}
Last Updated: : 9/16/2020, 11:34:40 AM