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
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
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
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