Note: I'm starting off at the end of Part 4.
The app, so far, is very nice. We have a list of people with some information and can page through them 10 at a time.
But let's add a little more. We're going add a feature that let's the user click on a movie the character was in and see the movie scroll that was at the start of the movie.
First we'll need to get a list of the movies the person was in. Like the homeworld
, each movie has a URL that provides the information. But Luke was in multiple movies, not just one. In the person
object instead of the URL being a string, it's an array of strings; an array of the movie URLs.
Making the call to the API
We'll need to add a new function to the store to make a series of calls to get all the movies.
Inside the store/StarWars.js
file, add this function to the StarWarsStore
object. Remember to add a comma between each function.
getMovies: (movies) => {
let apicall = Promise.all(movies.map(URLString =>
fetch(URLString)
.then(function(response) {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
))
.then(texts => { return texts });
return apicall;
}
The center portion (in the red rectangle; #1) is the same code as the getByURL
function. It's inside the arrow function used by movies.map
(#2). Map will cycle through the array. Each time the fetch is called it'll return a promise. That means the movies.map
will be returning an array of promises. And that's exactly what Promise.all
(#3) takes in. So what's going inside apicall
? A promise, who would have thought? Promise.all
takes a set of promises and will only be fulfilled when all the promises in the array of promises are fulfilled. The .then
(#4) portion will run and that's what will be returned when the Promise.all
is finally completely fulfilled.
Person.svelte
Now that we can make the call, now we just need to do it. Place this in the script section next to the declaration of HomeWorldInfo
.
let MovieInfo = StarWarsStore.getMovies(person.films);
And even though I've said it enough times that you'll hear it in your sleep, MovieInfo
is given a Promise of the API call we designed in StarWarsStore
.
Using the {#await}
from Svelte to wait for the promise to be fulfilled. And {#each}
to cycle through the results. Please note, the full promise will only be fulfilled if ALL promises are fulfilled, if one fails, the entire thing fails. And it will stay pending until they all fulfill or one fails. Place this after the {#await HomeWorldInfo}...{/await}
section.
{#await MovieInfo}
<h3>Loading....</h3>
{:then movies}
{#each movies as movie}
<hr>
<p>{movie.title}</p>
{/each}
{:catch error}
<p>Whoops</p>
{/await}
Where does the array come from that we are using {#each}
on? It's coming from the Promise.all
. As the promises are fulfilled an array is created with each fulfilled promise. Hence we are using {#each}
to go over each response in the array.
Now start this up and you'll see the names of the movies that each person was in.
Modal.svelte
We have the movie information and now we'll create a trigger to display a Modal box that will display the Movie Scroll.
First step is to create the component. In the components
folder create a file called Modal.svelte
and put the following in it.
<script>
export let showModal = false;
export let movie;
</script>
{#if showModal}
<div class="backdrop" on:click|self>
<div class="modal" >
<div class="crawl">
<h2>{movie.title}</h2>
<h3>Episode {movie.episode_id}</h3>
<p class="scroll">{movie.opening_crawl}</p>
</div>
</div>
</div>
{/if}
<style>
.backdrop {
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
background: rgba(0, 0, 0, 0.2);
z-index: 99;
}
.modal {
padding: 10px;
border-radius: 10px;
max-width: 500px;
margin: 10% auto;
text-align: center;
background: #222;
color: gold;
}
.scroll {
padding: 5px 30px;
line-height: 2.1;
font-size: 1.4;
font-family: 'Krona One', sans-serif;
text-align: justify;
}
</style>
The script portion is taking two values from the calling component. showModal
is a boolean that we're defaulting to false. This will be used for keeping track if the Modal should be shown or not. We're defaulting it to false so it won't be visible when the page first loads.
The second value is the movie information the Modal will be referencing.
The CSS doesn't have anything outside the normal Modal styling. If you aren't familiar with creating Modals, I'd highly recommend taking a look at the CSS and how it works.
There are two parts of the HTML section, we should to cover.
the {#if}
will display the code inside the if statement as long as showModal
is true. In this case we a using a straight boolean value. The {#if}
from Svelte would also take a conditional such as x==1
and if x is equal to 1, it would be true and would display the code.
{#if showModal}
...
{/if}
The on:click|self
is somewhat like the buttons we made before. But we're not providing the function it's going to call. If you don't provide a function to call, the event will be passed up to the calling component.
Now for |self
. This is a modifier of the event. There are a few modifiers available. In this case we are limiting the click event to only when the target of the click is the specific element that has the on:click
on.
This might seem odd, but in this case we only want to respond to the click on the backdrop and not on the Modal box itself. After we get this running, go ahead and remove this and test it out.
Person.svelte
So where are we going to use this modal? Well, the modal needs to know the information that Person.svelte
has information about. Remember we put the movie information in Person.svelte
component.
Add this to the script section of Person.svelte
.
import Modal from './Modal.svelte';
let showModal = false;
let selectedEpisode = 0;
let toggleModal = ({target}) => {
selectedEpisode = target.dataset.episodeid;
showModal = !showModal;
};
Importing the Modal.svelte
would look best near the top of the script section. It's good to group like items to make the code organized. When troubleshooting issues, properly formatting and grouping can make a huge difference in how much time you'll be digging around.
showModal
, which we also saw was being received by Modal.svelte
.
selectedEpisode
will be used to tell which movie was picked.
The toggleModal
will be used to grab the episodeid
and toggle the showModal
value.
Inside the HTML section, making the changes to the loop of movies so it reads:
{#each movies as movie}
<hr />
<p on:click={toggleModal} data-episodeid="{movie.episode_id}">{movie.title}</p>
{#if selectedEpisode == movie.episode_id}
<Modal {showModal} {movie} on:click={toggleModal} />
{/if}
{/each}
Most of this we've seen before, but we'll discuss this a bit.
<p on:click={toggleModal} data-episodeid="{movie.episode_id}">
{movie.title}
</p>
The <p>
will accept a click event and fires off the toggleModal
.
The data-*
attribute is being used to attach the episodeid
. It's used in the toggleModel
to store the selectedEpisode
.
<Modal {showModal} {movie} on:click={toggleModal} />
We're passing showModal
and movie
into the Modal
component.
Remember inside the Modal
component we had that on:click
that we didn't pass a function to execute during the event? Will, here we are declaring that any on:click
events being sent up from the component will call the function toggleModal
.
At this point we can run the app, click on a movie and see the movie scroll.
On from here
The app is complete, well, complete as far as I went. But there is a lot more that you could extend this simple app.
Some idea's to continue:
- Styling the movie scroll to actually scroll
- Add more information about the person
- Add a Modal that brings up the homeworld information
Thanks for joining me on this. I'm already working on my next post and I hope you'll join me on that as well.