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. This is not the alert() method. Embed the message in the HTML.
  • 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>
      <!-- SEARCH FORM GOES HERE -->
      <section id="search-results">
        <h2>Results</h2>
        <!-- ERROR MESSAGES GO HERE -->
      </section>
    </div>

    <script src="./main.js"></script>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
'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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
Last Updated: : 9/4/2021, 11:36:09 AM