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()
}