Zaczynamy implementować funkcjonalności localStorage. Pobieramy zadania


Uff... Znamy już podstawy pracy z localStorage, a nawet przetestowaliśmy dodawanie, pobieranie i usuwanie naszych zadań z localStorage za pomocą poleceń. Musimy teraz zaimplementować to w naszej aplikacji, a dokładnie zmodyfikować wszystkie dotychczasowe funkcje (a nawet deklarację zmiennej tasks!), by były zsynchronizowane z localStorage.

Utwórz zatem nowy plik o nazwie todo-app-console-local-storage-snippet w zakładce snippets i zaczynamy przenosić do niego i uzupełniać kod z wersji 1.0.

Być może się zastanawiasz, po co utworzyliśmy nowy plik, a nie modyfikujemy poprzedni, z którym pracowaliśmy wcześniej?

Otóż, jak najbardziej możemy modyfikować poprzedni plik z pierwszą wersją aplikacji, aczkolwiek wtedy ją utracimy. Owszem, być może nie będzie nam potrzebna, ponieważ nowa wersja będzie bardziej zaawansowana, aczkolwiek dobrą praktyką jest przechowywanie różnych wersji w różnych plikach.

W programowaniu nazywamy to kontrolą wersji i używa się do tego zaawansowanych narzędzi, tj. GitHub, aczkolwiek nam na razie wystarczy, że będziemy tworzyć nowy plik (lub później foldery z plikami) dla każdej wersji aplikacji, którą będziemy tworzyć. Ponadto, ten sposób organizacji naszego kodu pozwala nie tylko na wyizolowane korzystanie z każdej z wersji, ale także, w pewnym sensie, podróż w czasie, jeśli chodzi o naszą naukę, ale też rozwój aplikacji.

Zaczynamy!

Najpierw czyścimy konsolę, jak wcześniej:

console.clear();

// ...tu będziemy pisać kolejne liniki kodu

Teraz, zamiast deklarować zmienną tasks i przypisywać do niej pustą tablicę, zdefiniujmy funkcję getTasksFromLocalStorage(), której nazwa wskazuje na to, że będzie pobierać i zwracać zapisane zadania z localStorage. Funkcja ta musi nie tylko pobrać, ale i zwrócić nasze zadania, byśmy mogli potem zadeklarować zmienną tasks i przypisać do niej pobrane zadania podczas uruchomienia programu.

Identycznie działa każda aplikacja, która pobiera dane z bazy danych, np. kiedy uruchamiamy Messengera, to przez jakąś chwilę pobiera on pewną ilość danych z bazy - ostatnie konwersacje, nowe wiadomości itd.

// ...tutaj jest poprzedni kod w pliku

function getTasksFromLocalStorage() {
    // pobieramy tablicę zadań zapisaną w localStorage w postaci tekstowej:
    const storedTasksStringified = localStorage.getItem("tasks");

    // konwertujemy pobrane dane w JavaScriptową tablicę:
    const storedTasks = JSON.parse(storedTasksStringified);

    // UWAGA!
    // zadeklarowaliśmy powyższe zmienne za pomocą słowa kluczowego const, a nie let,
    // ponieważ nie będziemy tych zmiennych... zmieniać:
    // przypisujemy do nich pobraną wartość z localStorage lub też jej przekonwertowaną wersję,
    // a potem zwracamy tę zmienną (przekazujemy ją dalej do ponownego wykorzystania),
    // nie będziemy ich w żaden sposób modyfikować!

    // Jeśli nie pamiętasz, o co chodzi ze słowem kluczowym const,
    // wróć do lekcji nt. zmiennych w module 1. kursu JavaScript

    // PS. użycie słowa kluczowego let także zadziała,
    // aczkolwiek użycie const w tym przypadku będzie dobrą praktyką. 

    // zwracamy wartość:
    return storedTasks;
}

// zadeklarujmy zmienną tasks i przypiszmy do niej to,
// co zwróci funkcja getTasksFromLocalStorage():

let tasks = getTasksFromLocalStorage();

// funkcja showTasks() pozostaje bez zmian:

function showTasks() {
  console.log("Twoje zapisane zadania:", tasks);
}

// ...tu będzie kolejny kod

Przepisz powyższy kod do pliku, uruchom go i wpisz w konsoli, która się pojawiła, polecenie showTasks(). Co Ci się wyświetliło?

Jeśli zrobiłeś/aś wcześniej to, o co prosiłem (czyli po eksperymentach z localStorage usunąłeś/aś klucz "tasks"), to powinno Ci się wyświetlić tasks: null.

Co to znaczy? W JavaScript null jest specjalną wartością, która oznacza brak wartości. Klucz został usunięty z localStorage, więc jego wartość wynosi... nic. I to jest w porządku:

  • jeśli są zadania lub też zadania zostały usunięte, ale klucz "tasks" jest nadal dostępny w localStorage, to funkcja getTasksFromLocalStorage() zwróci tablicę (pustą lub zawierającą zadania),
  • jeśli z kolei klucz "tasks" został usunięty lub nie było go wcale, to funkcja zwróci null.

Jeśli po raz pierwszy odpalamy naszą aplikację na tej konkretnej stronie (pamiętasz, że localStorage przypisuje dane do konkretnego adresu url?), nigdy nie przechowywaliśmy tutaj zadań, w związku z czym ich wartość jest null. Podobnie jest w przypadku, gdy klucz został usunięty z localStorage.

No i mógłbyś/abyś zapytać: W czym problem? Funkcja zwróciła null i tyle.

Sprawdźmy zatem! Do kodu, który już masz w pliku, dodajmy zmodyfikowaną funkcję addTasks, która także wymaga utworzenia funkcji pomocniczej updateTasksInLocalStorage(), która będzie aktualizowała zadania w localStorage:

// ...tutaj jest poprzedni kod w pliku

// dodajemy funkcję aktualizującą tablicę zadań w localStorage
// (czyli nadpisującą jej poprzednią wartość):

function updateTasksInLocalStorage() {
    // aktualizujemy localStorage o właśnie zmienione tasks
    // pamiętaj, że musimy przekształcić tablicę w dane tekstowe,
    // dlatego używamy JSON.stringify():

    localStorage.setItem("tasks", JSON.stringify(tasks));

    // pamiętaj, że tasks jest zmienną globalną,
    // czyli zawsze mamy dostęp do jej aktualnej wartości
    // z poziomu każdej funkcji
}

function addTask(newTask) {
  // dodajemy zadanie do zmiennej, tak jak wcześniej:
    tasks.push(newTask);

    // uzupełniamy funkcję addTasks o aktualizację localStorage
    // za pomocą funkcji updateTasksInLocalStorage():

    updateTasksInLocalStorage();

  // tu pozostawiamy dotychczasowy kod:
    console.log("Do Twoich zadań zostało dodane nowe zadanie:", newTask);

    showTasks();
}

Zapisz teraz plik, wyświetl zadania w konsoli, a potem spróbuj dodać zadanie za pomocą funkcji addTasks("test"). I co się stało?

Powinien wyświetlić się komunikat błędu (error): Uncaught TypeError: Cannot read properties of null (reading 'push'). Dlaczego pojawił się błąd?

Przyczyną błędu jest, że próbowaliśmy zastosować metodę push() (w środku funkcji addTask()), która jest metodą właściwą dla tablic, na wartości null, która tablicą nie jest... Nie da się "wcisnąć" jakiejś wartości do czegoś, co nie istnieje, a null jest właśnie reprezentacją niczego.

Dlatego musimy upewnić się, że funkcja getTasksFromLocalStorage() zawsze zwraca tablicę, nawet jeśli nie mamy klucza reprezentującego zadania w localStorage (lub też jeśli do klucza "tasks" z jakiegoś powodu byłaby przypisana wartość null, co jak najbardziej możemy zrobić, gdybyśmy tego chcieli).

W tym celu musimy dodać mechanizm sprawdzający, czy wartość klucza "tasks" w localStorage jest tablicą (nieważne, czy pustą, czy też zawierającą zadania), czy też null i w tym drugim przypadku zamiast null funkcja powinna zwrócić po prostu pustą tablicę.

Żeby wdrożyć taki mechanizm, potrzebujemy... kolejnej dawki wiedzy! Dlatego teraz poznamy i zastosujemy po raz pierwszy konstrukcję warunkową if-else oraz powiązane z nią wartości logiczne true i false. Zacznijmy od true i false!

Nie przez przypadek przekreśliłem powyższe akapity. Chciałem Ci pokazać, że owszem, przekreślone zagadnienia są mega ważne i zdecydowanie zaliczają się do podstaw języka JavaScript oraz że owszem dzięki nim moglibyśmy wdrożyć wspomniany mechanizm sprawdzający, czy funkcja getTasksFromLocalStorage() zawsze zwraca tablicę, aczkolwiek...

Przed publikacją tego kursu przejrzałem jego treść jeszcze raz i stwierdziłem, że na tym etapie (w tym module oraz w kursie ogólnie) zależy mi na tym, abyście jak najszybciej zbudowali coś wartościowego, coś co działa, z czego możecie korzystać i jednocześnie wymaga jak najmniej wiedzy, żeby Was nie przytłoczyć jej nadmiarem. Moim celem jest Was zachęcić do spróbowania swoich sił w kodowaniu, dlatego...

Zamiast lektury dwóch większych rozdziałów (ponad 200 linijek tekstu i przykładów, które oczywiście i tak później wypłyną), zastosujemy pewien trick - skrót myślowy, który rozwiąże nasz problem w sekundę, bez nadmiaru wiedzy i przykładów, a jednocześnie jest powszechną i poprawną techniką sprawdzania czy wartość jest prawdziwa (istnieje). Użyjemy operatora logicznego || podczas zadeklarowania zmiennej tasks w ten sposób:

let tasks = getTasksFromLocalStorage() || [];

Zauważ, że po wywołaniu funkcji, która zwraca albo tablicę z zadaniami, albo null, czyli nic, co zdecydowanie nie jest tablicą i później doprowadzi do błędu, wstawiliśmy mały fragment kodu: || [].

Operator logiczny || (bardzo upraszczając) oznacza dosłownie lub (ang. or) i w tym przypadku działa w ten sposób: jeśli getTasksFromLocalStorage() zwraca "nic", czyli innymi słowami, jeśli nie ma zwróconych zadań, to przypisz do zmiennej tasks pustą tablicę.

Lub jeszcze inaczej: przypisz do zmiennej tasks realną/ rzeczywistą wartość zwróconą przez getTasksFromLocalStorage() (czyli np. tablicę) LUB (||) przypisz pustą tablicę. Zadania lub pusta tablica.

Od razu powiem, że jest to ogromne uproszczenie i wręcz spłaszczenie tego, co się dzieje naprawdę, ale uznałem, że na razie wystarczy nam taki trick - te tematy poruszymy we właściwym czasie!

  • zmodyfikuj zatem wcześniejszy kod, służący do deklaracji zmiennej let tasks = getTasksFromLocalStorage(); i zamień go na let tasks = getTasksFromLocalStorage() || [];,
  • zapisz plik,
  • uruchom go

i zobaczysz, że w konsoli po odpaleniu programu znowu pojawił się komunikat o wartości null klucza "tasks".

Teraz wyświetl listę zadań (showTasks()), która powinna zwrócić pustą tablicę!

No i teraz możesz spróbować dodać jakieś zadanie: addTask("zadanie testowe").

Mało tego, że tym razem udało się w końcu dodać zadanie, to w dodatku zostało ono zapisane w localStorage (ponieważ addTask() zawiera teraz dodatkową funkcję updatejtującą localStorage po dodaniu zadania updateTasksInLocalStorage())!

Możesz to sprawdzić, uruchamiając kod jeszcze raz (kliknij ikonkę trójkąta lub Ctrl+Enter lub przeładowując stronę i uruchamiając plik). Tym razem nie zobaczysz żadnego komunikatu o wartości null. Spróbuj teraz wpisać showTasks() i powinieneś/aś zobaczyć to: tasks: ['test']. Udało się!

Fajnie by było również, gdyby aplikacja po uruchomieniu od razu pokazała nam nasze zapisane zadania lub też ich brak. Dodaj więc wywołanie funkcji showTasks() na końcu pliku (pamiętaj, że JavaScript czyta i wywołuje kod od góry do dołu, dlatego zainicjuje wszystkie zmienne i funkcje, a potem wywoła showTasks(), która będzie już miała dostęp do pobranych (lub nie) zadań).