Kolejny projekt, w którym korzystami z API. Tym razem dynamicznie tworzymy elementy DOM w oparciu o dane z API i korzystamy z bardziej zaawansowanych rozwiązań.

Templatka startowa plus wytyczne

Zaczynamy z tym kodem:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <p id="joke">Loading joke...</p>
    <label for="category">Category</label>
    <select name="category" id="category">
        <option value="all">All</option>
    </select>
    <button id="jokeBtn">Load joke</button>
    <script>
        window.addEventListener('DOMContentLoaded', function(){
        console.log("DOM fully loaded");
        });
    </script>
</body>
</html>

Chcemy:

  • połączyć się ze znanym już nam Chuck Norris API i zaciągnąć losowy dowcip umieszczając go w tagu <p>
  • połączyć się z endpointem Chuck Norris API zwracającym wszystkie możliwe kategorie i w oparciu o dane zwrotne utworzyć elementy <option> w naszym <select>
  • dać możliwość użytkownikowi zaciągania kolejnych dowcipów, także z ustawioną przez niego kategorią.

Zaciągnięcie dowcipu na „dzień dobry” – powtórka

Łapiemy nasz paragraf przez querySelector, tworzymy stałą z adresem API, korzystamy z fetch:

window.addEventListener('DOMContentLoaded', function(){
   let jokeP = document.querySelector("#joke");
   const API_URL = "https://api.chucknorris.io/jokes/random";
   fetch(API_URL)
   .then(response => response.json())
   .then(data => data.value)
   .then((joke) => jokeP.textContent = joke);
 });

Cały ten „.then” pipe działa w ten sposób:

  • wyjmij z odpowiedzi json (czyli pomiń różne nagłówki, statusy odpowiedzi 200, wyjmij mi przesłaną treść odpowedzi) i przekaż dalej
  • weź to, co przekazał poprzedni .then, nazwij to data i wyciągnij z tego klucz value (pod tym kluczem znajduje się treść dowcipu)
  • weź to, co przekazał poprzedni .then, nazwij to joke i wklej jako zawartość tekstową do elementu jokeP (nasz <p>)

Zaciągnięcie kategorii

Tworzymy stałą dla URLa, z którego pobieramy kategorie:

const API_CAT_URL = "https://api.chucknorris.io/jokes/categories";

Tworzymy szkielet funkcji, która dodaje kategorie do <select>

 function createOption(category) {
     console.log(category);
 }

Teraz zaciągamy kategorie (które przychodzą w postaci listy) z naszego API i wrzucamy w funkcję createOption każdą kategorię z osobna:

fetch(API_CAT_URL)
   .then(response => response.json())
   .then(data => {
      data.forEach((cat) => createOption(cat));
});

Jak widać loguje nam każdą z osobna.

Funkcja createOption – tworzenie elementów DOM

Musimy złapać element <select>, do którego będziemy dodawać opcje:

let select = document.querySelector("#category");

Teraz bierzemy się za tworzenie elementu <option> w funkcji createOption:

function createOption(category) {
   let newOption = document.createElement('option');
   newOption.value = category;
   newOption.textContent = category;
   select.appendChild(newOption);
}

Musimy jeszcze gdzieś zapisywać wybraną kategorię:

let select = document.querySelector("#category");
let selectedCat = select.value;

Potrzebna nam jeszcze funkcja, która obsługuje zmianę kategorii:

function changeCategoryHandler() {
    selectedCat = select.value;
    console.log(selectedCat);
}

Na sam koniec musimy dodać event-listener, który wywołuje funkcję za każdym razem, gdy zmieni się wybrana kategoria:

function changeCategoryHandler() {
  selectedCat = select.value;
  console.log(selectedCat);
}
select.addEventListener('change', changeCategoryHandler);

W zasadzie działa. Mnie osobiście razi natomiast, że textContent naszych opcji jest pisany małą literą. Kategorie są mają literą (value naszego <option>), ale tekst nie musi być.

Dodajmy tę sprytną funkcję do naszego projektu:

function capitalizeFirstLetter(string) {
    return string.charAt(0).toUpperCase() + string.slice(1);
}

Teraz użyjmy jej w odpowiednim miejscu – czyli tam, gdzie dodajemy textContent (value musi być małą literą, inaczej API nie zrozumie, ale użytkownik nie jest API):

function createOption(category) {
   let newOption = document.createElement('option');
   newOption.value = category;
   newOption.textContent = capitalizeFirstLetter(category);
   select.appendChild(newOption);
}

Teraz wszystkie nasze kategorie dla użytkownika zapisane są wielką literą, natomiast logowane w konsoli małą, w postaci takiej, jaką API rozumie.

Dodajemy główną funkcjonalność

Teraz, w zależności od tego, czy kategoria jest ustawiona na „all” czy na cokolwiek innego, będziemy mieli różny URL. Na początku złapmy sobie nasz guzik:

let jokeBtn = document.querySelector("#jokeBtn");

Teraz dodajmy event-listener z funkcją wybierania odpowiedniego endpointa:

jokeBtn.addEventListener('click', function(){
  const endpoint = selectedCat === 'all' ? API_URL : `${API_URL}?category=${selectedCat}`;
  console.log(endpoint);
});

Teraz kategoria ustawiona na all loguje w konsoli:

https://api.chucknorris.io/jokes/random

Kategoria ustawiona na animal loguje nam:

https://api.chucknorris.io/jokes/random?category=animal

Teraz już z górki, jak mamy poprawny endpoint:

jokeBtn.addEventListener('click', function(){
    const endpoint = selectedCat === 'all' ? API_URL : `${API_URL}?category=${selectedCat}`;
    joke.textContent = "Ładowanie";
    fetch(endpoint)
    .then(response => response.json())
    .then(data => data.value)
    .then((joke) => jokeP.textContent = joke);
});

To wszystko. Projekt działa, jak należy.