Um página com as intros do melhores filmes ever.
Você deve usar a Star Wars API (https://swapi.dev) para poder (a) carregar dinamicamente a lista de filmes e (b) também para exibir o respectivo texto introdutório quando o usuário selecionar um filme.
Dê uma explorada na Star Wars API. Será necessário fazer apenas 1 chamada para pegar todos os filmes.
Além disso, inclua o arquivo starwars.js
em index.html
como um módulo.
Veja como incluir um módulo JS). Ele será
o único arquivo incluído no index.html
, porque os outros serão import
ados
dentro dele.
Obs: o arquivo starwars.js
ainda não foi incluído. A melhor forma
para incluí-lo é no HEAD da página usando o atributo defer
(para que comece a ser baixado o quanto antes, mas só seja executado
depois do navegador terminar o parsing da página).
Vamos colocar certa música pra tocar (e você sabe qual). Via JavaScript, pôr uma música pra tocar é tão simples quanto:
const musica = new Audio(url)
musica.play()
Você pode fazer isso, ou então usar um music player prontinho, com interface
gráfica pronta (dê uma lidinha no arquivo music.js
).
Ele exporta uma função play(...)
que espera receber 2 parâmetros:
- Objeto com informações da música:
- URL do áudio:
audio/tema-sw.mp3
- URL da capa:
imgs/logo.svg
- Título: Intro
- Artista: John Williams
- URL do áudio:
- Elemento HTML onde o player será injetado (no caso,
document.body
)
Ou seja, (a) importe essa função play
do music.js
no seu
arquivo starwars.js
e (b) invoque-a passando 2 argumentos referentes
aos parâmetros especificados. Para saber os nomes das propriedades
do 1º parâmetro, veja a assinatura da função play
em music.js
.
Faça uma requisição Ajax (ajax nos slides) para pegar a lista
de filmes e, quando a tiver, preencha a #filmes ul
com um li
para cada
filme (slides sobre como inserir elementos dinamicamente).
Cada li
deve ter o texto "Episode ROMANO - TITULO", em que ROMANO é
o algarismo romano referente ao número do filme e TITULO é o título.
Para a chamada Ajax, você deve usar o fetch
, que retorna uma promessa
(fetch
nos slides) e pode trabalhar tanto com a
promessa diretamente ou por meio de async/await
(slides sobre async-await).
A resposta da chamada Ajax com dados sobre os filmes não traz o número
do filme em romano. Então, será necessário converter de algarismo
decimal para romano. Isso deve ser feito em um módulo roman.js
que export
essa funcionalidade e seja import
em starwars.js
.
Convertendo de romano para decimal...
Uma ideia legal aqui é aproveitar o fato de que (a) são poucos filmes,
(b) que objetos em JavaScript são dicionários e que (c) é possível
acessar suas propriedades se soubermos seu nome em uma string
ao usar a notação colchetes (ie, obj[prop]
).
Por exemplo, veja como poderíamos fazer pra converter emojis de frutas para nomes de frutas:
function emojiParaNome(emoji) {
const dados = {
'🍎': 'Maçã',
'🍍': 'Abacaxi',
'🥝': 'Kiwi',
'🍓': 'Morango'
}
// retorna o valor da propriedade cujo
// nome é o emoji do parâmetro
return dados[emoji]
}
//...
console.log(`Gosto de ${emojiParaNome(🍎)}`)
Se desejar deixar o título de cada filme alinhado (como na segunda imagem), você pode preencher a string referente ao número romano para que ela tenha no mínimo um comprimento definido.
Como preencher uma string com espaços...
Strings possuem dois métodos interessante:
s.padStart(tamanho, caractere)
e s.padEnd(tamanho, caractere)
.
Esses métodos repetem o caractere
uma quantidade de vezes
suficiente para que s
tenha pelo menos o tamanho
. Caso
s.length >= tamanho
, nada acontece. Caso contrário,
é retornada uma string preenchida com tamanho - s.length
caracter
no início ou no fim de s
.
Exemplo:
let alunos = ['Lestat', 'Rui', 'Adamastor']
alunos = alunos.map(aluno => aluno.padEnd(10, '-'))
// [
// 'Lestat----',
// 'Rui-------',
// 'Adamastor-'
// ]
Ao clicar em um dos episódios, você deve carregar a introdução do filme
em pre.introducao
. O texto a ser colocado nesse elemento deve ser composto
por:
Episode ROMANO
TITULO
CONTEÚDO
...seguindo essas quebras de linha (e linha em branco). Além de definir
o conteúdo de pre.introducao
conforme definido, para reiniciar a animação
(texto comece a subir novamente), invoque a função de restart-animation.js
.
Como colocar evento de 'click'
ao <li>
recém-criado?
Para tratar um evento (eg, click
) de um elemento que estamos
criando dinamicamente, há diferentes opções:
- Event delegation (não vimos em aula): em vez de registrar
o
click
em cada<li>
, registramos no pai dele. Dentro da função handler do evento, perguntamos quem é oevt.target
(não confunda comevt.currentTarget
¹). Verifica-se se otarget
é um dos<li>
da<ul>
e, em caso afirmativo, descobre-se de qual filme aquele<li>
se refere. Isso pode ser feito, por exemplo, por meio de atributos de dados colocados no<li>
(ie,<li data-episode-id="...">
). - Inserir novo elemento usando
document.createElement('li')
: quando usamos a "forma mais burocrática" para criar novos elementos, podemos configurá-lo antes de inserí-lo na árvore. Ou seja, podemos chamaraddEventListener(...)
no próprio<li>
. - Inserir novo elemento criando um fragmento de DOM: nessa abordagem,
temos um template (código HTML dentro de uma string do JavaScript) mas,
em vez de fazer
pai.innerHTML += template
, criamos um fragmento de DOM, configuramos ele e, então, inserimos no DOM real. Dessa forma podemos chamaraddEventListener(...)
em qualquer elemento do template antes que ele seja inserido do DOM oficial.
¹evt.target
vs evt.currentTarget
: apesar de parecidos, essas propriedades podem ter valores diferentes. Enquanto currentTarget
sempre aponta exatamente para o elemento em que registramos o evento (nesse caso, chamamos addEventListener
na #filmes ul
), o target
pode ser um dos filhos/descendentes de quem sofreu o evento (ou seja, pode ser uma das <li>
).
Antes de inserir os elementos referentes a cada filme (as li
s),
ordene os filmes de acordo com seu número, de forma que o primeiro
seja o filme I.
Em JavaScript, vetores possuem o método v.sort(funcaoComparadora)
.
Veja a documentação de v.sort()
na MDN.
Para exercitar a criação de promessas e também Web Storage, você deve
criar uma função para substituir o fetch
e vamos chamá-lo de friendlyFetch
.
Ele é amigável porque sempre que faz uma requisição Ajax, salva o resultado
(depois do .json()
) em cache no localStorage
usando a URL requisitada
como chave. E antes de delegar para a chamada do fetch
original, verifica
se já não possui a resposta no cache, evitando sobrecarregar a API de
Star Wars. Crie essa função em um módulo friendly-fetch.js
.
Lembre-se que o Web Storage salva apenas strings, então você pode precisar serializar um objeto antes de salvar e também dessearializar depois que recuperar o valor salvo lá. Veja slides sobre JSON.
É possível criar uma promessa diretamente ou como valor de retorno de uma
função async
.
Criando uma promessa...
function friendlyFetch(url) {
const promessaDeRequisicao = new Promise((resolve, reject) => {
// ...
// executa algo assíncrono (eg, chama fetch(url))
// ...
// eventualmente chama resolve(resultado)
// e se der erro, chama reject(erro)
})
return promessaDeRequisicao
}
Retorno de função async...
async function friendlyFetch(url) {
// ...
// executa algo assíncrono (eg, chama fetch(url))
// possivelmente aguardando o resultado do await
// ...
// retorna o resultado
}
Para lembrar como usar localStorage
, veja os
slides sobre Web Storage.
- Como enviar requisições assíncronas (Ajax)?
- Usar XMLHttpRequest diretamente
- Usar fetch
- Como usar o
localStorage
?- Veja nos slides sobre Web Storage