Aplikacja todo
W poniższym tekście spróbujemy zmierzyć się z raczej prostą aplikacją, która pozwalać będzie nam zapisywać i edytować zadania w bazie danych. Kod napiszemy z podziałem na pliki i przy użyciu dodatkowych narzędzi bundlujących, czyli zbierzemy wiedzę z ostatnich kilku rozdziałów. Tutaj od razu mała uwaga. Poniższe podejście z wykorzystaniem bundlerów i podziałem na osobne pliki nie jest wymagane by stworzyć taki mini projekt. Rób po prostu jak ci wygodniej.
Wstęp
Demo ma wyłączone operacje na bazie, więc nie wszystkie funkcjonalności będą w nim działać.
W poniższym projekcie będziemy do korzystać z parcela do bundlowania plików, oraz json-server jako małej bazy danych.
Po pobraniu paczki, rozpakuj ją do jakiegoś katalogu, a następnie zainstaluj wszystko poleceniem npm i. Przejrzyj strukturę plików i zerknij na wygląd bazy danych w katalogu data.
My będziemy pracować w katalogu src, natomiast parcel będzie naszą pracę na bieżąco kompilować do katalogu dist.
Jak już się zaznajomisz ze strukturą projektu, odpal całość poleceniem npm start. Zadanie to odpala dwa zadania na raz. Wpierw odpali się json-server miło nas witając, a zaraz potem parcel. Dostaniesz w terminalu dwa adresy - jeden na który będziemy się łączyć, a drugi z aktualnym widokiem strony.
W poniższym tekście będę zaznaczał z jakiego pliku jest dany kod. Zakładam, że stosowne pliki będziesz w razie potrzeby tworzył.
Wczytanie zadań
Zacznijmy od najprostszej rzeczy, czyli wczytania zadań z bazy. Stwórz plik _api.js i dodaj do niego funkcję:
//_api.js
const apiUrl = "http://localhost:3000/tasks"; //tutaj twój adres do json-server, który pokazał się w terminalu
export async function apiGetTasks() {
const request = await fetch(apiUrl);
if (request.ok) {
return request.json();
} else {
throw Error(request.status);
}
}A następnie zaimportuj ją w pliku app.js:
Sprawdź w konsoli czy dostaniesz odpowiedni wynik. Jeżeli tak, możemy przejść do wstępnego wyświetlenia zadań. Do tego celu napiszemy odpowiednie funkcje. Jedna będzie generować kod zadania, druga będzie tworzyć pojedyncze zadanie, a trzecia wykorzysta je do stworzenia całej listy:
Dodawanie zadania
Dodawanie zadania podzielmy na 2 części. Po pierwsze musimy dodać je do bazy danych:
Po drugie musimy obsłużyć formularz na stronie przy okazji korzystając z powyższej funkcji:
Po dodaniu i wyświetleniu zadania na liście, użytkownik z pewnością tego nie zauważy, ponieważ ląduje ono na końcu listy.
Rozwiązania są dwa - albo zamiast wrzucać go na koniec (append), możemy wrzucać go na początek listy (prepend). Zmienimy to na końcu funkcji renderSingleTask().
Możemy też automatycznie przewijać listę do dodanego zadania:
Usuwanie zadań
To samo wykonamy w przypadku usuwania zadań. Wpierw komunikacja z serwerem:
A następnie wykorzystanie tej funkcji po kliknięciu na guzik usuwania zadania. Takie kliknięcie moglibyśmy podpiąć wewnątrz funkcji renderSingleTask pobierając odpowiedni element, natomiast możemy też skorzystać z propagacji zdarzeń:
Usuwając zadanie po prostu je wycinamy ze strony. Takie "chamskie" usuwanie od strony użytkownika nie jest najlepszym rozwiązaniem, ponieważ przy podobnych zadaniach na stronie nie zauważy on ich usuwania, bo kolejne momentalnie wleci na miejsce właśnie usuniętego. Aby to poprawić możemy pokusić się o dodanie tuż przed usunięciem subtelnej animacji zwijania. Do takich animacji najlepiej skorzystać z dodatkowej biblioteki - np. greensock. Ja tym razem skorzystam z dostępnego w nowych przeglądarkach api animate:
Wyszukiwanie zadań
Kolejna dość podobna rzecz do poprzednich. Wpierw funkcja pobierająca dane:
Zauważ, że w adresie zapytania do serwera przekazujemy parametr q=... - zgodnie z instrukcją json-server.
Wykorzystajmy powyższą funkcję podpinając się pod formularz wyszukiwania:
Przy bardzo szybkim wpisywaniu frazy do takiego pola możemy przyblokować połączenie, ponieważ będziemy robić wiele zapytań w bardzo krótkim czasie. Aby tego uniknąć możemy zastosować funkcję spowalniającą debounced, którą omawialiśmy tutaj. Stwórz plik _utility.js i dodaj do niego odpowiednią funkcję (a można i dwie):
Edycja zadania
Najgorsze na koniec. Po kliknięciu na przycisk edycji w zadaniu powinniśmy dane elementy zamienić na odpowiednie kontrolki, a dodatkowo dodać do zadania przycisk Zapisz i Anuluj.
Zacznijmy jednak od funkcji zmieniającej dane na serwerze:
Podobnie do usuwania obsłużmy kliknięcie na przycisk edycji w zadaniu:
Musimy zmienić funkcję getTaskHTML(dataElement), która od tej pory będzie też zwracała kod edytowanego zadania:
A także funkcje, które z niej korzystają:
Dodatkowo musimy obsłużyć też dwa przyciski, które nam doszły:
Zastosowanie template
Jeżeli robisz aplikację w całości napisaną w Javascript (np. używając jakiegoś frameworka typu Vue), na sztywno zakodowany html w twoich skryptach nie powinien być raczej problematyczny. Zdarza się jednak, że twoja aplikacja, a w zasadzie wygląd html będzie zależał od backendu i to właśnie "tył" powinien ci go jakoś dostarczyć. Rozwiązań na to jest przynajmniej naście (np. podobnie do zadań możesz taki kod pobierać z bazy), natomiast jednym z najczęściej stosowanych jest użycie elementu template.
Spróbujmy je zastosować w naszej aplikacji. Dodajmy więc odpowiednie elementy do naszej strony (na końcu body):
a następnie użyjmy ich w funkcji generującej html dla zadania:
Tutaj mała uwaga. Nasz template trzymamy bezpośrednio w html. Dla zaznajomionego z webem użytkownika nie będzie żadnym problemem by go podmienić, i wstrzyknąć do niego potencjalnie szkodliwy kod. Żeby temu zapobiec, moglibyśmy na zakończenie powyższej obróbki potraktować go dodatkową biblioteką - np. DOMPurify. Wpierw przerwij działanie projektu (kilka razy Ctrl + C w terminalu),zainstaluj tą bibliotekę poleceniem npm i dompurify, a następnie dodaj do pliku _render.js:
Handlebars
Niestety z powyższym rozwiązaniem jest pewien problem. Obecnie mamy dwa stany - spoczynkowy i edycji. A gdyby takich stanów było nieco więcej - np 5? Oczywiście możemy stworzyć pięć oddzielnych templatów w html, ale może to później spowodować uciążliwą ich edycję, bo dodanie nowego elementu wymusi na nas edycję w 5 miejscach na raz. Zamiast tego możemy skorzystać tutaj z bardziej rozbudowanych templatek, które pozwalają w swoim kodzie używać pętli, ifów i dodatkowych instrukcji. Jedną z najbardziej popularnych jest handlebars.
Przerwij działanie projektu i zainstalują tą bibliotekę poleceniem npm i handlebars.
Następnie zamień powyższe templaty na jeden wspólny (odpowiednie helpery masz opisane tutaj):
A następnie wykorzystanie go w funkcji generującej kod zadania:
Obsługa błędów
W powyższych kodach zakładaliśmy, że każde połączenie do bazy danych zakończy się sukcesem, a przecież nie zawsze będzie to prawdą. Dodajmy więc obsługę błędów w postaci odpowiednich komunikatów dla użytkownika. Moglibyśmy tutaj użyć console.log, ale w moim odczuciu console jest dobre dla osób które wiedzą, że istnieje coś takiego jak debugger - czyli nei dla zwykłych użytkowników. Zamiast tego pokażmy błędy bezpośrednio na stronie. Wykorzystamy do tego bibliotekę toast-me. Podobnie do poprzednich zainstaluj ją poleceniem npm i toast-me, a następnie dodaj ją przy wyświetlaniu komunikatów:
Last updated