Uwaga! Trwają prace nad nową wersją serwisu. Mogą występować przejściowe problemy z jego funkcjonowaniem. Przepraszam za niedogodności!

⛔ Potrzebujesz wsparcia? Oceny CV? A może Code Review? ✅ Dołącz do naszej społeczności na Discordzie!

Obsługa błędów w formularzu JavaScript – podstawy

Korzystaj z tablic i obiektów zamiast alertów

Poziom podstawowy: jak usprawnić weryfikację pól formularza i wyświetlać komunikaty o błędach? Zrezygnuj z alertów i console.log(), czas przejść na wyższy level. Gdy zrozumiesz już, jak gromadzić komunikaty o błędach w tablicach i pokazywać je w DOM-ie, czeka Cię kolejny artykuł podlinkowany na końcu – o ograniczeniu liczby instrukcji warunkowych. Powodzenia!

Spis treści

 Przygotowanie formularza w pliku HTML

W tym przykładzie posłużymy się prostym formularzem do wpisania danych adresowych. Jego kod wygląda w ten sposób:

<!-- novalidate – wyłączam sprawdzanie poprawności danych przez przglądarkę, aby swobodnie testować własne rozwiązanie -->
<form action="" method="post" novalidate>
    <div>
        <label>
            Imię
            <input name="firstName" pattern="^[a-zA-Z –-]+$" required />
        </label>
    </div>
    <div>
        <label>
            Nazwisko
            <input name="lastName" pattern="^[a-zA-Z –-]+$" required />
        </label>
    </div>
    <div>
        <label> Ulica <input name="street" required /> </label>
        <label>
            Numer budunku
            <input name="houseNumber" type="number" required />
        </label>
        <label>
            Numer mieszkania <input name="flatNumber" type="number" />
        </label>
    </div>
    <div>
        <label>
            Kod pocztowy
            <input name="zip" pattern="^[0-9]{2}-[0-9]{3}$" required />
        </label>
        <label>
            Miejscowość
            <input name="city" pattern="^[a-zA-Z –-]+$" required />
        </label>
    </div>
    <div><button type="submit">Wyślij</button></div>
</form>

Cały kod przedstawionego w tym artykule rozwiązania znajdziesz w repozytorium na GitHubie.

 Alerty i długie instrukcje warunkowe

 Wyświetlanie komunikatów o błędach w okienku alert

Komunikaty dotyczące formularza moglibyśmy wyświetlać użytkownikowi jako alerty, np. tak:

const formEl = document.querySelector('form');

// sprawdzam, czy formularz został wyszukany i dopiero przypisuję nasłuchiwanie zdarzenia submit
if (formEl) {
  formEl.addEventListener('submit', handleSubmit);
}

function handleSubmit(e) {
  e.preventDefault(); // blokuję automatyczne wysłanie formularza, by móc sprawdzić ewentualne błędy

  // jeśli pole nie zostało wypełnione...
  if (formEl.firstName.value.length === 0) {
    // ...wyświetl alert z treścią komunikatu
    alert('Pole Imię nie może być puste!');
  } else if (formEl.lastName.value.length === 0) {
    alert('Pole Nazwisko nie może być puste!');
  } else if (formEl.street.value.length === 0) {
    alert('Pole Ulica nie może być puste!');
  } // itd.
}

Jeśli jednak użytkownik pomyli się w kilku polach i podsumuje formularz (kliknie „wyślij”, „dalej” itp.), otrzyma alert z pierwszego niepoprawnego pola. Poprawi błąd, znów kliknie przycisk podsumowujący i znów dostanie alert – tym razem z kolejnego pola. Ten proces będzie musiał powtarzać tak długo, aż wszystkie pola będą poprawne.

Niezbyt przyjazne pod kątem UX (doświadczenia użytkownika, ang. user experience), prawda?

Możemy też zarzucić użytkownika kilkoma alertami naraz, które będzie musiał wyłączyć jeden po drugim:

if (formEl.firstName.value.length === 0) {
  alert('Pole Imię nie może być puste!');
}
if (formEl.lastName.value.length === 0) {
  alert('Pole Nazwisko nie może być puste!');
}
if (formEl.street.value.length === 0) {
  alert('Pole Ulica nie może być puste!');
} // itd.

Z jednej strony lepiej: użytkownik od razu otrzymuje informację o wszystkich błędach. Lecz co, jeśli przy poprawkach zapomni, gdzie jeszcze się pomylił? Nadal więc UX cierpi!

Kod powyższego rozwiązania znajdziesz w repozytorium, a działanie podejrzysz tutaj. Aby testować kolejne rozwiązania razem ze mną, sforkuj repozytorium i uruchom u siebie.

 Wyświetlanie wszystkich błędów na liście

W pliku HTML stwórzmy miejsce na komunikaty, czyli listę nieuporządkowaną <ul>:

<!-- novalidate – wyłączam sprawdzanie poprawności danych przez przglądarkę, aby swobodnie testować własne rozwiązanie -->
<form action="" method="post" novalidate>
  <ul class="messages"></ul>
    <div>
      <label>
        Imię
          <input name="firstName" pattern="^[a-zA-Z –-]+$" required />
       </label>
    </div>
<!-- (...)  -->

Dzięki temu, gdy któreś z pól zostanie błędnie wypełnione, od razu będziemy mogli utworzyć nowy element listy (czyli <li>), uzupełnić go treścią komunikatu i dodać do drzewa DOM. W ten sposób użytkownik dostanie pełną listę błędów.

const formEl = document.querySelector('form');

// wyszukuję w drzewie DOM listę dla komunikatów
const messagesList = document.querySelector('.messages');

// sprawdzam, czy formularz został wyszukany i dopiero przypisuję nasłuchiwanie zdarzenia submit
if (formEl) {
  formEl.addEventListener('submit', handleSubmit);
}

function handleSubmit(e) {
  e.preventDefault(); // blokuję automatyczne wysłanie formularza, by móc sprawdzić ewentualne błędy

  messagesList.innerText = ""; // czyszczę listę błędów po każdym submicie formularza, by wyświetlić tylko błędy bieżące

  if (formEl.firstName.value.length === 0) {
    // tworzę nowy element listy
    const liEl = document.createElement('li');
    // dodaję do elementu listy treść: komunikat
    liEl.innerHTML = 'Pole Imię nie może być puste';
    // dodaję element listy komunikatów
    messagesList.appendChild(liEl);
  }
  if (formEl.lastName.value.length === 0) {
    const liEl = document.createElement('li');
    liEl.innerHTML = 'Pole Nazwisko nie może być puste';
    messagesList.appendChild(liEl);
  }
  if (formEl.street.value.length === 0) {
    const liEl = document.createElement('li');
    liEl.innerHTML = 'Pole Ulica nie może być puste';
    messagesList.appendChild(liEl);
  } // itd.
}

Zwróć uwagę, że za każdym uruchomieniem funkcji handleSubmit czyszczę listę <ul> – ustawiam jej właściwość innerText na pusty string. Dzięki temu komunikaty błędów w DOM-ie nie będą się duplikować.

Jeżeli chcesz podejrzeć działanie powyższego kodu, to w pliku HTML w elemencie <script> podmień wartość atrybutu src na "errorslist.js".

Powyższe rozwiązanie możemy jeszcze usprawnić – nie powielać kodu tworzącego elementy <li> i skrócić instrukcje warunkowe. Z pomocą przychodzą nam tablice.

 Komunikaty o błędach w tablicy

Spójrz, w poniższym kodzie pojawiła się zmienna errors przechowująca pustą tablicę. To do niej będziemy dodawać komunikaty błędów.

const formEl = document.querySelector('form');

// wyszukuję w drzewie DOM listę dla komunikatów
const messagesList = document.querySelector('.messages');

// sprawdzam, czy formularz został wyszukany i dopiero przypisuję nasłuchiwanie zdarzenia submit
if (formEl) {
  formEl.addEventListener('submit', handleSubmit);
}

function handleSubmit(e) {
  e.preventDefault(); // blokuję automatyczne wysłanie formularza, by móc sprawdzić ewentualne błędy

  messagesList.innerText = ''; // czyszczę listę błędów po każdym submicie formularza, by wyświetlić tylko błędy bieżące

  const errors = []; // tworzę tablicę, w której będę zbierać komunikaty błędów

  if (formEl.firstName.value.length === 0) {
    // jeśli pole nie zostało wypełnione, dodaję komunikat do tablicy `errors`
    errors.push('Pole Imię nie może być puste');
  }
  if (formEl.lastName.value.length === 0) {
    errors.push('Pole Nazwisko nie może być puste');
  }
  if (formEl.street.value.length === 0) {
    errors.push('Pole Ulica nie może być puste');
  } // itd.

  // jeśli tablica z błędami nie jest pusta, to iteruję po niej i tworzę elementy <li> dla każdego dodanego do niej komunikatu
  if (errors.length !== 0) {
    errors.forEach(function (error) {
      // tworzę nowy element listy
      const liEl = document.createElement('li');
      // dodaję do elementu listy treść: komunikat
      liEl.innerHTML = error;
      // dodaję element listy komunikatów
      messagesList.appendChild(liEl);
    });
  }
}

Dzięki zapisywaniu komunikatów w tablicy, mogliśmy znacznie skrócić instrukcje warunkowe. Teraz nie powtarzamy już kodu tworzącego elementy <li>. Zawarliśmy go w jednym miejscu: wewnątrz funkcji wywoływanej w metodzie forEach. Iterujemy po tablicy errors i dla każdego komunikatu w niej zawartego tworzymy nowy element <li>.

Efekt jest taki sam, jak wcześniej (błędy wyświetlają się na liście), lecz teraz nasz kod jest trochę łatwiejszy w obsłudze.

Jeżeli chcesz podejrzeć działanie powyższego kodu, to w pliku HTML w elemencie <script> podmień wartość atrybutu src na "errorsarray.js".

 Komunikaty o błędach w obiekcie i tablicach

Może nam zależeć na tym, aby wyświetlać błędy nie na ogólnej liście, a bezpośrednio pod polem formularza, które zostało błędnie wypełnione. Wówczas możemy gromadzić błędy w tablicy przypisanej do właściwości obiektu o nazwie pola, czyli w takiej formie:

// tworzę obiekt, którego właściwości odpowiadają nazwom pól formularza
const errors = {
  firstName: [],
  lastName: [],
  street: [],
};

Zobaczmy, jak obsługa takich błędów może wyglądać w kodzie:

const formEl = document.querySelector('form');

// wyszukuję w drzewie DOM listę dla komunikatów
const messagesList = document.querySelector('.messages');

// sprawdzam, czy formularz został wyszukany i dopiero przypisuję nasłuchiwanie zdarzenia submit
if (formEl) {
  formEl.addEventListener('submit', handleSubmit);
}

function handleSubmit(e) {
  e.preventDefault(); // blokuję automatyczne wysłanie formularza, by móc sprawdzić ewentualne błędy

  messagesList.innerText = ''; // czyszczę listę błędów po każdym submicie formularza, by wyświetlić tylko błędy bieżące

  // tworzę obiekt, którego właściwości odpowiadają nazwom pól formularza
  const errors = {
    firstName: [],
    lastName: [],
    street: [],
  };

  if (formEl.firstName.value.length === 0) {
    // jeśli pole nie zostało wypełnione, dodaję komunikat do tablicy o odpowiedniej nazwie w obiekcie `errors`
    errors.firstName.push('Pole Imię nie może być puste');
  }
  if (formEl.lastName.value.length === 0) {
    errors.lastName.push('Pole Nazwisko nie może być puste');
  }
  if (formEl.street.value.length === 0) {
    errors.street.push('Pole Ulica nie może być puste');
  } // itd.

  // dla przykładu dodajmy kolejny warunek, który sprawdza, czy pole zawiera same litery
  if (!formEl.firstName.value.match(/^[a-zA-Z –-]+$/)) {
    // jeśli pole jest błędnie wypełnione, dodaję komunikat do tablicy o odpowiedniej nazwie w obiekcie `errors`
    errors.firstName.push('Pole Imię może zawierać tylko litery!');
  }

  // dla każdego klucza w obiekcie `errors`...
  for (const field in errors) {
    // sprawdzam, czy mam już elementy <p> z błędami:
    // -> formEl[field] to pole formularza w DOM-ie (korzystamy z tego, że nazwa klucza w obiekcie i nazwa pola w pliku HTML są takie same);
    // -> jego parentElement to <label> – wewnątrz niego szukamy elementów <p> (dodaję je poniżej w przypadku błędów)
    const errorMsgEls = formEl[field].parentElement.querySelectorAll('p');
    
    if (errorMsgEls.length !== 0) {
      // jeśli takie elementy <p> są, to je usuwam, by się nie zduplikowały
      Array.from(errorMsgEls).forEach(element => element.remove());
    }
    if (errors[field].length !== 0) {
      // iteruję po każdej tablicy z błędami dla danego pola, jeśli nie jest ona pusta
      errors[field].forEach(function (message) {
        // tworzę element <p> dla błędu
        const errorMsgEl = document.createElement('p');
        // uzupełniam go treścią błędu
        errorMsgEl.innerText = message;
        // dodaję element <p> do elemntu <label>, który jest rodzicem dla elementu <input>
        formEl[field].parentElement.appendChild(errorMsgEl);
      });
    }
  }
}

Ponieważ nasze błędy są teraz przechowywane w obiekcie, używamy pętli for…in, dzięki której możemy iterować po kluczach obiektu. W pętli sprawdzamy, czy któraś z tablic zawiera komunikaty o błędach. Jeśli tak, to wyświetlamy je w postaci paragrafów (elementów <p>) pod adekwatnym polem.

Zobacz: dzięki temu, że nazwy kluczy w obiekcie (firstName, lastName, street) odpowiadają nazwom pól formularza w pliku HTML, mogliśmy wyszukać je w DOM-ie automatycznie: formEl[field] w pętli zostanie podmienione na formEl["firstName"], formEl["lastName"], formEl["street"] itd.

Warto zapamiętać ten sposób, bo przyda się na dalszym etapie nauki.

Jeżeli chcesz podejrzeć działanie powyższego kodu, to w pliku HTML w elemencie <script> podmień wartość atrybutu src na "errorsobject.js".

 Jak tworzyć mniej instrukcji warunkowych

Jeżeli rozumiesz powyższe przykłady i chcesz dowiedzieć się, jak tworzyć mniej instrukcji warunkowych przy sprawdzaniu pól formularza, zapraszam Cię do artykułu: „Walidacja formularza w JavaScript”.

Udostępnij ten artykuł:

Mentoring to efektywna nauka pod okiem doświadczonej osoby, która:

  • przekazuje Ci swoją wiedzę i nadzoruje Twoje postępy w zdobywaniu umiejętności,
  • uczy Cię dobrych praktyk i wyłapuje złe nawyki,
  • wspiera Twój rozwój i zwiększa zaangażowanie w naukę.

Mam coś dla Ciebie!

W każdy piątek rozsyłam motywujący do nauki programowania newsletter!

Dodatkowo od razu otrzymasz ode mnie e-book o wartości 39 zł. To ponad 40 stron konkretów o nauce programowania i pracy w IT.

PS Zazwyczaj wysyłam 1-2 wiadomości na tydzień. Nikomu nie będę udostępniał Twojego adresu e-mail.