Autocomplete
Naszym zadaniem będzie stworzenie prostego pola z listą podpowiedzi.
W czystym HTML taką listę możemy uzyskać za pomocą znacznika datalist:
<input list="browsers">
<datalist id="browsers">
<option value="Internet Explorer">
<option value="Firefox">
<option value="Google Chrome">
<option value="Opera">
<option value="Safari">
</datalist>Podaj przeglądarkę
Zanim zaczniemy, ściągnij paczkę z początkowym projektem, który będzie nam służył za backend. To prosty skrypt korzystający z Express, który wypluwa dane brane z plików json. Dodatkowo przygotowany jest tam prosty html, w którym umieściłem tylko jedno pole tekstowe, które będziemy poddawać obróbce.
Po ściągnięciu zainstaluj wszystko poleceniem npm i, a następnie odpal całość poleceniem npm start. Dostaniesz do użytku dwa adresy na które będziemy się łączyć. Pierwszy to http://localhost:3000/cities a drugi http://localhost:3000/users.
Klasa Autocomplete
Zacznijmy od stworzenia prostej klasy, na bazie której będziemy tworzyć pola autocomplete.
class Autocomplete {
constructor(input, options) {
//pobieramy pole
this.input = input;
//nasze opcje - ta sama technika jaką używaliśmy przy lightboxie, sliderze i innych zadaniach
this.options = {...{
url : ""
}, ...options};
this.makeHTML();
}
}
const auto = new Autocomplete(document.querySelector("#testCities"), {
url : "http://localhost:3000/cities"
})Konstruktor ma za zadanie pobrać pole, które będziemy modyfikować, ustawić podstawowe opcje i wywołać funkcję makeHTML(), która utworzy strukturę html.
Funkcja makeHTML
Pierwszą z funkcji będzie makeHTML().
Tworzymy dynamicznie listę oraz podpinamy ją do pola. Do takiego podpięcia służy atrybut list, do którego przekazujemy id listy. Do ustawienia jego użyjemy zewnętrznej zmiennej autoCompleteID.
Można by się tutaj pokusić o udoskonalenie nadawania takiego id. W powyższym kodzie lista dostaje id np. autocompleteList-0, autocompleteList-1 itd. A jeżeli na naszej stronie istnieje już element o takim id? Moglibyśmy dodatkowo sprawdzać czy taki element istnieje. Jeżeli tak - próbujemy dalej:
Ja zrezygnuję z tego podejścia by nie udziwniać kodu.
Funkcja bindEvents
Podczas wpisywania będziemy odpytywać serwer o wyniki. Żeby nie robić tego za często skorzystamy z techniki debounced, którą omawialiśmy tutaj.
Do funkcji debounced przekazaliśmy funkcję this.checkInputValue, którą napiszemy za moment. Po odpaleniu zwykłej funkcji (a więc też tej, którą przekazujesz przez referencję) w jej wnętrzu this zostaje zmienione na "coś". W powyższym przypadku przekazaną przez referencję funkcję odpalamy później wewnątrz setTimeout (we wnętrzu którego this i tak już wskazuje na window - czemu?). Funkcja ta nie jest odpalana jako metoda obiektu (przed nazwą funkcji nie ma kropki i obiektu np. ob.fn(...args)), stąd this zostało by zmienione i wskazywało na window. Za pomocą bind() (linia 20) wymuszamy więc by wewnątrz funkcji słowo this wskazywało na właściwy obiekt. Hej - ale my już o tym mówiliśmy.
Cały powyższy kod moglibyśmy też napisać nieco prościej korzystając z funkcji strzałkowej (zauważ, że metoda checkInputValue() odpalana jest jako metoda naszego obiektu - taka mała rzecz, a zmienia wszystko):
Funkcja checkInputValue
Funkcja checkInputValue() sprawdza tylko długość wartości pola. Gdy jest odpowiednio długa, wywołamy zapytanie do serwera.
Aż się prosi aby cyfra 3 była pobierana z opcji naszej klasy.
Jeżeli chcesz, doda odpowiednią opcję w konstruktorze, a następnie zmień powyższy kod na:
Funkcja makeRequest
Kolejną funkcją będzie ta robiąca już właściwe połączenie.
Za pomocą składni aync/await oraz fetch wykonujemy połączenie, pobiera dane, a następnie przekazuje je do funkcji fillList(), która wypełni listę odpowiednimi opcjami. W przypadku błędu rzucamy błędem.
Dodatkowo żeby zasygnalizować użytkownikowi moment wczytywania pokażemy mu ikonkę wczytywania.
Do pokazania wczytywania posłużą nam dwie funkcje. Pierwsza z nich tworzy element o klasie .autocomplete-loading, z druga po prostu go usuwa. Odpowiednie stylowanie dla tego elementu znajdziesz w pliku css ściągniętej paczki.
Funkcja fillList
Ostatnia funkcja posłuży do wypełnienia listy odpowiednimi opcjami.
I tyle. Nasza klasa jest gotowa.
Cache wyników
Gdy zaczniesz wpisywać nazwę miasta, zostanie wykonane zapytanie do serwera. Jeżeli znowu zaczniesz wpisywać taką samą nazwę, nasz skrypt ponownie wykona odpowiednie zapytanie i dostanie te same wyniki. Spróbujmy to usprawnić, dodając proste zapamiętywanie danych (cache). Pobrane wyniki będziemy odkładać do zmiennej. Jeżeli użytkownik ponownie wpisze takie same dane, my zamiast robić zapytanie do serwera pobierzemy dane z naszego magazynu.
Zauważ, że zmienna cache jest globalna. Założyłem, że jeżeli będziemy mieli na stronie kilka instancji naszego obiektu, wszystkie one będą korzystać ze wspólnego cache.
Własna lista rozwijana
Jako bonus zamieszczam kod dla autocomplete z listą opartą o element ul. Dzięki temu nie tylko możemy ją dowolnie stylować, ale też zmieniać zawartość jej elementów.
Ogólne działanie będzie tutaj podobne do tego co powyżej. Główna różnica wynika z tego, że domyślna lista datalist na której bazowaliśmy powyżej ma już zaimplementowaną całą funkcjonalność - np. wybieranie elementu czy poruszanie się za pomocą klawiszy. Jeżeli robimy taką listę sami, wszystko musimy też zakodować. Funkcja bindEvents() stała się więc bardziej rozbudowana. Dodatkowo zauważysz, że w kilku miejscach doszło nam wywoływanie pokazania i ukrywanie listy (funkcje showList() i hideList()) oraz zaznaczanie aktywnego elementu.
Kolejnymi rzeczami różniącymi się od poprzedniego rozwiązania jest wydzielenie osobnej funkcji dla tworzenia pojedynczego elementu listy (renderElement()) oraz dla wybrania elementu (selectActive()).
Dzięki temu tworząc pojedynczy obiekt autocomplete mogę mu te funkcje nadpisać zmieniając wyświetlanie elementów na liście, ale też zmieniając zwracane dane przy wyborze elementu.
Last updated