Formularz kontaktowy

Czasami ludzie na naszej grupie pytają się, jak wykonać prosty formularz kontaktowy.
Z chęcią bym rzucił im linkiem do jakiegoś fajnego arykułu, ale po przeglądnięciu Googla nie znalazłem nic co by się specjalnie mi spodobało. Oczywiście są jakieś artykuły, ale osobiście nigdy nie znalazłem kompleksowego tekstu.
W poniższym tekście nadrobimy tą lukę i stworzymy prosty formularz kontaktowy ze wszystkim czego potrzeba by to realnie działało.

Naszą pracę podzielimy na 4 części:

  • Stworzenie html
  • Stylowanie html
  • Podpięcie logiki js
  • Podpięcie logiki backendowej

HTML

Zaczynamy od prostego kodu html:

<form class="form" id="contactForm" method="post" novalidate action="/send-scripts.php">
    <div class="form-row">
        <label for="field-name">Name*</label>
        <input type="text" name="name" required id="field-name" data-error="Wypełnij to pole" pattern="[a-zA-ZąĄććę곣ńŃóÓśŚżŻŹŹ ]+">
    </div>
    <div class="form-row">
        <label for="field-email">Email*</label>
        <input type="email" name="email" required id="field-email" data-error="Wpisz poprawny email" pattern="[^@\s]+@[^@\s]+\.[^@\s]+">
    </div>
    <div class="form-row">
        <label for="field-message">Message*</label>
        <textarea name="message" required data-error="Musisz wypełnić pole" id="field-message" pattern=".+"></textarea>
    </div>
    <div class="form-row">
        <label class="checkbox-cnt">
            <input type="checkbox" data-error="Musisz wypełnić pole" name="regulation">
            <i class="state" aria-hidden="true"></i>
            <span>Lorem ipsum dolor sit amet, consectetur adipiscing elit. In libero arcu, vulputate sit amet mattis sit amet, ultrices in erat. Aenean suscipit arcu ac lorem lacinia ut scelerisque turpis commodo.</span>
        </label>
    </div>
    <div class="form-row">
        <button type="submit" class="submit-btn">
            Wyślij
        </button>
    </div>
</form>

Ważne by każdy label wskazywał na pole, które ma taki sam id jak atrybut for labela.
Dla checkboxa zastosowałem kod który dawno temu pokazałem tutaj: http://domanart.pl/wtf-forms/.
Każde wymagane pole posiada atrybut required. Wykorzystamy go do pobrania pól które będą sprawdzane.
Aby podczas wysyłania formularza nie pojawiały się domyślne przeglądarkowe dymki walidacji, dla formularza nadaliśmy właściwość novalidate, która wyłącza domyślną walidację.

Jak słusznie zauważył Commander, atrybut novalidate najlepiej dodać za pomocą skrypt. Dzięki temu szasz formularz nie straci
mocy walidacji, gdy użytkownik będzie miał wyłączone JS.

Pola tekstowe dostały też atrybut pattern, który w formie regexp określa wymaganą treść. Zauważ, że dla pola typu email też podałem ten atrybut, mimo, że domyślnie to pole też jest sprawdzane przez przeglądarkę. Nie jestem teraz pewien, czy domyślna walidacja jest poprawna, bo przepuszcza maile w stylu example@, a z tego co wiem po małpie powinny być jakieś znaki. Jeżeli masz lepszą wiedzę na ten temat, wystarczy, że usuniesz ten atrybut z tego pola.
Każde pole ma także dodatkowy atrybut data-error, który zawiera tekst, który będzie wyświetlany w razie błędu.

CSS

Do naszego formularza dodajemy proste stylowanie:

* {
    box-sizing: border-box;
}

.form {
    margin:3rem auto;
    font-family:sans-serif;
    max-width:40rem;
}
.form .form-row {
    margin-bottom:1rem;
}
.form .form-row:last-child {
    margin-bottom: 0;
}
.form input[type=text],
.form input[type=email],
.form textarea,
.form .checkbox-cnt .state {
    box-shadow:inset 0 1px 5px rgba(0,0,0,0.07);
}
.form input[type=text],
.form input[type=email],
.form textarea {
    border-radius:0.2rem;
    font-family:sans-serif;
    padding:0.8rem;
    border:1px solid #aaa;
    display: block;
    width:100%;
    color:#666;
}
.form input[type=text]:focus,
.form input[type=email]:focus,
.form textarea:focus {
    border-color:#9DC9F5;
    box-shadow:inset 0 0 0 1px #9DC9F5, inset 0 1px 5px rgba(0,0,0,0.07);
    outline:none;
}
.form textarea {
    height:10rem;
}
.form label {
    font-weight:bold;
    display: block;
    font-size:0.9rem;
    margin-bottom:0.5rem;
}
.form .submit-btn {
    font-family: sans-serif;
    padding:1rem 2rem;
    background: #70B81B;
    border:0;
    border-radius:0.2rem;
    color:#fff;
    font-size:1.1rem;
    font-weight: bold;
    transition: 0.3s background-color;
}

/* checkbox */
.form .submit-btn:hover {
    background: #7EC927;
}
.form .checkbox-cnt {
    padding-left:3rem;
    position: relative;
    font-weight: normal;
    font-size:0.85rem;
    line-height: 1.1rem;
    color:#444;
    cursor: pointer;
}
.form .checkbox-cnt .state {
    width:2rem;
    height: 2rem;
    display: block;
    position: absolute;
    left:0;
    top:0;
    border:1px solid #aaa;
    border-radius:0.2rem;
}
.form .checkbox-cnt .state:before {
    width:1rem;
    height: 1rem;
    border-radius:0.2rem;
    background: #70B81B;
    display: block;
    position: absolute;
    left:50%;
    top:50%;
    content:'';
    transform:translate(-50%, -50%) scale(1);
    opacity:0;
}
.form .checkbox-cnt input:checked ~ .state:before {
    animation: checkboxShowAnim 0.5s 1;
    opacity: 1;
}
.form .checkbox-cnt input {
    position:absolute;
    top:0; left:0;
    width:2rem;
    height: 2rem;
    z-index: 2;
    cursor:pointer;
    padding:0;
    margin:0;
    opacity: 0;
}
@keyframes checkboxShowAnim {
    0%  { border-radius:50%; transform:translate(-50%, -50%) scale(0.2); }
    50% { transform:translate(-50%, -50%) scale(1.2); }
    100% { transform:translate(-50%, -50%) scale(1); }
}

/* walidacja */
.form input[type=text].error,
.form input[type=email].error,
.form textarea.error,
.form .checkbox-cnt input.error ~ .state {
    border-color:##E01546;
}
.field-error {
    color:##E01546;
    padding:0.5rem 0;
    font-size:0.8rem;
}

@media screen and (max-width:500px) {
    .form .submit-btn {
        display: block;
        width: 100%;
    }
}

Połowa stylowania dotyczy się checkboxa i była przedstawiona we wspomnianym artykule. W tej chwili uzyskaliśmy efekt:

Formularz wygląd

Logika po stronie przeglądarki

Przykładowe stworzenie walidacji formularzy opisywałem w tym tekście. W powyższym przykładzie wykorzystamy przedstawioną tam metodę, choć sama walidacja będzie o wiele prostsza.

Zanim przejdziemy do kodowania rozpiszmy (koniecznie na kartce z wykorzystaniem ołówka :) logikę działania formularza:

  1. Pola naszego formularza powinny mieć dynamiczną walidację, która będzie reagować na wpisywany tekst. Nie powinna ona być nachalna, czyli raczej nie będziemy użytkownika bombardować dodatkowymi opisami, a co najwyżej będziemy zmieniać wygląd pola
  2. Przy wysyłaniu formularza sprawdzimy wszystkie pola i w razie czego wyświetlimy dodatkowe opisy pod polami co należy wpisać.
  3. Jeżeli wszystkie pola są poprawne, wtedy wysyłamy formularzy, pokazujemy znacznik wczytywania (loading) i wyłączamy przycisk submit, tak by użytkownik nie mógł „mashować” nachalnej wysyłki
  4. Jeżeli przesłane na serwer dane z formularza są błędne, serwer powinien zwrócić informacje które pola są błędne, a my powinniśmy wyświetlić odpowiednie informacje
  5. Jeżeli formularz został wysłany poprawnie (wszystkie dane są ok i nie było błędu wysyłki) wtedy powinna pojawić się informacja o powodzeniu wysłania emaila

Po rozpisaniu tych punktów przejdźmy do napisania prostego skryptu walidacji. Użyjemy do tego celu jQuery:

$(function() {
    var $inputs = $('form input[required], form textarea[required], select[required]');

    var displayFieldError = function($elem) {
        var $fieldRow = $elem.closest('.form-row');
        var $fieldError = $fieldRow.find('.field-error');
        if (!$fieldError.length) {
            var errorText = $elem.attr('data-error');
            var $divError = $('<div class="field-error">' + errorText + '</div>');
            $fieldRow.append($divError);
        }
    };

    var hideFieldError = function($elem) {
        var $fieldRow = $elem.closest('.form-row');
        var $fieldError = $fieldRow.find('.field-error');
        if ($fieldError.length) {
            $fieldError.remove();
        }
    };

    $inputs.on('input keyup', function() {
        var $elem = $(this);
        if (!$elem.get(0).checkValidity()) {
            $elem.addClass('error');
        } else {
            $elem.removeClass('error');
            hideFieldError($elem);
        }
    });

    $inputs.filter(':checkbox').on('click', function() {
        var $elem = $(this);
        var $row = $(this).closest('.form-row');
        if ($elem.is(':checked')) {
            $elem.removeClass('error');
            hideFieldError($elem);
        } else {
            $elem.addClass('error');
        }
    });
})

Powyższy kod stworzy nam dynamiczną walidację, która w zależności od tego czy wpisana wartość jest poprawna czy nie
doda do pola klasę error lub ją usunie. Walidację dla pól tekstowych podpieliśmy pod zdarzenie
input. Do sprawdzenia poprawności skorzystaliśmy
z html5 js validation, która całą robotę robi za nas. Tak naprawdę w tym miejscu to API zbytnio się za nas nei napracowało, bo wystarczyło by pobrać z pola atrybut patern i przyrównać do niego wartość pola:

//zamiast
if (!$elem.get(0).checkValidity()) {
    ...
}

//możemy spokojnie zastosować klasyczną metodę z regexp
var pattern = $elem.attr('pattern');
if (new RegExp(pattern).test($elem.val()) {
    ...
}

Jeżeli nie podoba ci się walidacja na zdarzenie input, zastosuj zdarzenie change.

Dynamiczna walidacja

Dodanie tekstów walidacyjnych

Przy wysyłaniu formularza powinniśmy dokonać ponownej walidacji, tym razem jednak wyświetlimy teksty pod polami, które będą mówić użytkownikowi co zrobił nie tak:

$(function() {
    var $inputs = $('form input[required], form textarea[required], select[required]');

    var displayFieldError = function($elem) {
        ...
    };

    var hideFieldError = function($elem) {
        ...
    };

    $inputs.on('input', function() {
        ...
    });

    $inputs.filter(':checkbox').on('click', function() {
       ...
    });

    var checkFieldsErrors = function($elements) {
        //ustawiamy zmienną na true. Następnie robimy pętlę po wszystkich polach
        //jeżeli któreś z pól jest błędne, przełączamy zmienną na false.
        var fieldsAreValid = true;
        $elements.each(function(i, elem) {
            var $elem = $(elem);
            if (elem.checkValidity()) {
                hideFieldError($elem);
                $elem.removeClass('error');
            } else {
                displayFieldError($elem);
                $elem.addClass('error');
                fieldsAreValid = false;
            }
        });
        return fieldsAreValid;
    };

    $('.form').on('submit', function(e) {
        e.preventDefault();

        var $form = $(this);

        //jeżeli wszystkie pola są poprawne...
        if (checkFieldsErrors($inputs)) {
            var dataToSend = $form.serializeArray();
            dataToSend = dataToSend.concat(
                $form.find('input:checkbox:not(:checked)').map(function() {
                    return {"name": this.name, "value": this.value}
                }).get()
            );

            $.ajax({
                url : $form.attr('action'),
                method: $form.attr('method'),
                dataType : 'json',
                data : dataToSend,
                success: function(ret) {

                },
                error : function(error) {

                },
                complete: function() {
                }
            });
        }
    })
})

Na początku funkcji checkFieldsErrors ustawiamy zmienną fieldsAreValid na true. Następnie robimy pętlę po wszystkich polach które do niej przekazaliśmy, i jeżeli któreś z nich jest niepoprawne, wtedy zmienna ta ustawiana jest na false.
Funkcja w rezultacie zwraca true lub false w zależności od tego czy pola są poprawne czy nie.

Reszta kodu to klasyczne podpięcie pod formularz zdarzenia submit. Na początku wyłączamy domyśle zdarzenie (wysłanie formularza z przeładowaniem strony) za pomocą e.preventDefault(). Jeżeli wszystkie pola są poprawne (czyli funkcja checkFieldsErrors() zwróci true) możemy zająć się wysyłką za pomocą ajaxa.
Na początku pobieram url i method z parametrów formularza.
Następnei zbieramy dane formularza do zmiennej dataToSend za pomocą jquerowej metody serializeArray.
Domyślnie niezaznaczone checkboxy nie są wysyłane, więc dodajemy wartość checkboxa do zmiennej dataToSend za pomocą kodu:

var dataToSend = $form.serializeArray();
dataToSend = dataToSend.concat(
    $form.find('input:checkbox:not(:checked)').map(function() {
        return {"name": this.name, "value": this.value}
    }).get()
);

Co robi ten kod? Concat łączy tablice. Czyli w naszym przypadku dataToSend z wynikiem tego kodu. Reszta kodu pobiera niezaznaczone checkboxy, a następnie mapuje tablicę z wynikami i zwraca obiekty o kluczach name i value.
Dzięki temu mamy załatwione z marszu wsztystkie pola. Ale jeżeli nie lubisz takich dziwny kodów, dla tak małego formularza
spokojnie możesz ustawić dataToSend ręcznie:

...

dataToSend = {
    name : $form.find('input[name="name"]).val(),
    email : $form.find('input[name="name"]).val(),
    message : $form.find('input[name="message"]).val(),
    regulation : $form.find('input[name="regulation"]).val(),
}

...

Walidacja przed wysyłką

Wysyłka formularza

Pierwsze dwa punkty mamy w zasadzie załatwione. W tym momencie możemy już wysłać formularz a i nasza walidacja po stronie przeglądarki działa. Przechodzimy do punktu trzeciego naszego planu.
Przed samym wysłaniem danych wyłączamy przycisk submit. Dzięki temu zniecierpliwiony użytkownik nie będzie mógł na przypał klikać w przycisk wysyłania. Dodatkowo pokazujemy jakiś wskaźnik wczytywania. Możemy to zrobić jakąś graficzką loadingu, ale lepiej zastosować jakąś prostą animację. W necie znajdziecie takich całkiem sporo (
css loading), ale ja polecam użyć poniższej – bardzo prostej w zastosowaniu:

.elem-is-busy {
    position: relative;
    pointer-events: none;
    opacity:0.5;
}
.elem-is-busy::after {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    border: 2px solid rgba(0, 0, 0, 0.2);
    border-right-color: rgba(0,0,0,0.7);
    transform: translate(-50%, -50%) rotate(0deg);
    content:'';
    animation: rotateSingleLoading 0.3s infinite linear;
    z-index: 100;
}
@keyframes rotateSingleLoading {
    from {
        transform: translate(-50%, -50%) rotate(0deg);
    }
    to {
        transform: translate(-50%, -50%) rotate(360deg);
    }
}

Zastosujmy ją w naszym skrypcie:

$('.form').on('submit', function(e) {
   ...

    if (checkFieldsErrors($inputs)) {
        ...

        var $submit = $form.find('input:submit');
        $submit.prop('disabled', 1);
        $submit.addClass('element-is-busy');

        $.ajax({
            url : $form.attr('action'),
            method : $form.attr('method'),
            dataType : 'json',
            data : dataToSend,
            success: function(ret) {

            },
            error : function(error) {
                console.error('Wystąpił błąd z połączeniem');
            },
            complete: function() {
                $submit.prop('disabled', 0);
                $submit.removeClass('element-is-busy');
            }
        });
    }
})

Po czarnej stronie serwera

Formularz wysłaliśmy na serwer. Czas zająć się napisaniem skryptu serwerowego. Do naszych celów użyjemy PHP:

<?php
$mailToSend = 'xxxxx@wp.pl';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $name = $_POST['name'];
    $email = $_POST['email'];
    $message = $_POST['email'];
    $regulation = $_POST['regulation'];
    $errors = Array();
	$return = Array();
    if (empty($name)) {
        array_push($errors, 'name');
    }
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        array_push($errors, 'email');
    }
    if (empty($message)) {
        array_push($errors, 'message');
    }
    if (empty($regulation)) {
        array_push($errors, 'regulation');
    }
    if (count($errors) > 0) {
        $return['errors'] = $errors;
    } else {
        $headers  = 'MIME-Version: 1.0' . "\r\n";
        $headers .= 'Content-type: text/html; charset=UTF-8'. "\r\n";
        $headers .= 'From: '.$email."\r\n";
        $headers .= 'Reply-to: '.$email;
        $message  = "
            <html>
                <head>
                    <meta charset=\"utf-8\">
                </head>
                <style type='text/css'>
                    body {font-family:sans-serif; color:#222; padding:20px;}
                    div {margin-bottom:10px;}
                    .msg-title {margin-top:30px;}
                </style>
                <body>
                    <div>Imię: <strong>$name</strong></div>
                    <div>Email: <a href=\"mailto:$email\">$email</a></div>
                    <div class=\"msg-title\"> <strong>Wiadomość:</strong></div>
                    <div>$message</div>
                </body>
            </html>";

        if (mail($mailToSend, 'Wiadomość ze strony - ' . date("d-m-Y"), $message, $headers)) {
            $return['status'] = 'ok';
        } else {
            $return['status'] = 'error';
        }
    }

    header('Content-Type: application/json');
    echo json_encode($return);
}

Jest to prosty skrypt, który kolejno sprawdza przesłane pola. Jeżeli któreś z pól jest błędne, wtedy wrzuca
do tablicy $errors pod klucz będący nazwą tego pola tekst z komunikatem błędu.
W rezultacie gdy wystąpią błędy, do skryptu po stronie przeglądarki trafi odpowiedź w formacie json:

{
    "errors" : [
        "name",
        "email",
        "message",
        ...
    ]
}

Gdy dostaniemy z serwera powyższą odpowiedź, zrobimy pętlę po takiej tablicy errors i dla każdego pola o danej nazwie zastosujemy wcześniej napisaną funkcję fieldsHasErrors().

Gdy jednak wszystkie pola będą wypełnione poprawnie, powyższy skrypt tworzy prostą wiadomość email, wysyła ją za pomocą funkcji mail() i wysyła prostą odpowiedź:

{
    "status" : "ok"
}

Jeżeli by się okazało, że mail nie mógł być wysłany (np. błąd z serwerem), wtedy wróci do nas zwrotka z jsonem:

{
    "status" : "error"
}

Odpowiedź z serwera – błędne dane

Serwer dostał paczkuszkę danych, obsłużył ją i zwraca nam odpowiedź. W piewszej kolejności obsłużmy odpowiedź z wypisanymi błędnie wypełnionymi polami:

$('.form').on('submit', function(e) {
        ...

        if (checkFieldsErrors()) {
            ...

            $.ajax({
                url : $form.attr('action'),
                method : $form.attr('method'),
                dataType : 'json',
                data : dataToSend,
                success: function(ret) {
                    if (ret.errors) {
                        ret.errors.map(function(el) {
                            return '[name="'+el+'"]'
                        });
                        checkFieldsErrors($form.find(ret.errors.join(','));
                    } else {
                        if (ret.status=='ok') {
                            //wyświetlamy komunikat powodzenia, cieszymy sie
                        }
                        if (ret.status=='error') {
                            //komunikat błędu, niepowodzenia
                        }
                    }
                },
                error : function() {
                    console.error('Wystąpił błąd z połączeniem');
                },
                complete: function() {
                    $submit.prop('disabled', 0);
                    $submit.removeClass('element-is-busy');
                }
            });
        }
    })

Gdy dostaniemy w odpowiedz błędy ret.errors, każdą wartość tej tablicy (które są tak naprawdę nazwami naszych pól) zmianiamy za pomoą map na teksty np [name=”email”]. Następnie taką tablicę łączymy joinn(‚,’) dzięki czemu uzyskujemy zapis:

$form.find('[name="name"], [name="email"], [name="message"]...

Co w wyniku pobiera nam wszystkie błędne pola, które przekazujemy do funkcji checkFieldsErrors(). Sprytne.

Odpowiedź z serwera – błąd serwera

Pozostały nam w zasadzie pierdoły, czyli obsługa odpowiedzi, gdy status równy jest „ok” i „error”.
Zacznijmy znowu od błędów. Dla error możemy dodać do formularza tekst z komunikatem błędu wysyłki. Gdzie? A to zależy od naszych upodobać – może być np. po prawej stronie przycisku wyślij?

$form.find('.send-error').remove();
$.ajax({
    url : $form.attr('action'),
    method : $form.attr('method'),
    dataType : 'json',
    data : dataToSend,
    success: function(ret) {
        if (ret.errors) {
            ret.errors.map(function(el) {
                return '[name="'+el+'"]'
            });
            checkFieldsErrors($form.find(ret.errors.join(','));
        } else {
            if (ret.status=='ok') {
                //wyświetlamy komunikat powodzenia, cieszymy sie
            }
            if (ret.status=='error') {
                $submit.after('<div class="send-error">Wysłanie wiadomości się nie powiodło</div>');
            }
        }
    },
    error : function() {
        console.error('Wystąpił błąd z połączeniem');
    },
    complete: function() {
        $submit.prop('disabled', 0);
        $submit.removeClass('element-is-busy');
    }
});

Dodajmy też proste stylowanie dla tego komunikatu:

.form .send-error {
    display:inline-block;
    font-family: sans-serif;
    padding:1rem 2rem;
    color:red;
}
@media screen and (max-width:500px) {
    .form .send-error {
        text-align:center;
        display: block;
    }
}

Odpowiedź z serwera – wreszcie pozytywnie

Uff. Zajmijmy się teraz pozytywną odpowiedzią. Możemy tutaj oczywiście wstawić tekst tak samo jak w przypadku błędu serwera. Ja polecam ci jednak zastąpienie całego formularza html z informacją o pozytywnej wysyłce. Dzięki temu formularz będzie trochę bardziej odporny na różne prymitywne ataki:

$form.find('.send-error').remove();
$.ajax({
    url : $form.attr('action'),
    method : $form.attr('method'),
    dataType : 'json',
    data : dataToSend,
    success: function(ret) {
        if (ret.errors) {
            ret.errors.map(function(el) {
                return '[name="'+el+'"]'
            });
            checkFieldsErrors($form.find(ret.errors.join(',')));
        } else {
            if (ret.status=='ok') {
                $form.replaceWith('<div class="form-send-success"><strong>Wiadomość została wysłana</strong><span>Dziękujemy za kontakt. Postaramy się odpowiedzieć jak najszybciej</span></div>');
            }
            if (ret.status=='error') {
                $submit.after('<div class="send-error">Wysłanie wiadomości się nie powiodło</div>');
            }
        }
    },
    error : function() {
        console.error('Wystąpił błąd z połączeniem');
    },
    complete: function() {
        $submit.prop('disabled', 0);
        $submit.removeClass('element-is-busy');
    }
});
.form-send-success {
    font-family: sans-serif;
    text-align:center;
    font-size:2rem;
    font-weight:bold;
    border:1px solid #eee;
    color:#333;
    padding:10rem 0;
    margin:3rem auto;
    max-width:40rem;
}
.form-send-success strong {
    display:block;
    margin-bottom:0.5rem;
}
.form-send-success span {
    font-size:1rem;
    color:#888;
    font-weight:normal;
    display: block;
}

W zasadzie tyle naszej pracy. Formularz mamy gotowy.
Paczkę z naszym dziełem możesz pobrać tutaj.

Gotowy formularz

(dla testów wyłączyłem wysyłanie wiadomości)

Mini bonus – słodki antyspam

Powyżej stworzyliśmy prosty w pełni dynamiczny formularz kontaktowy.
Problem z takimi formularzami jest taki, że bardzo lubią je roboty rozsyłające spam. Aby im przeszkodzić musimy jakoś zabezpieczyć nasze dzieło. Jedną ze sprytniejszych metod jest tak zwana
honeypot. Polega ona na dodaniu do formularza dodatkowego pola, które ukrywamy za pomocą styli:

<span class="form-row honey-row">
    <label for="honey">Jeżeli jesteś człowiekiem, nie wypełniaj tego pola</label>
    <input type="text" name="honey">
</span>
.form .honey-row {
    display:none;
}

Większość robotów ignoruje style, ani nie rozumie języka pisanego, więc wypełnią to pole. Wystarczy teraz po stronie serwera
sprawdzić czy pole to ma wartość. Jak ma, olewamy całą sprawę, ewentualnie pokrzepiając robota miłymi słowami wiadomości w formie sukcesu:

<?php
$mailToSend = 'xxxxx@wp.pl';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $name = $_POST['name'];
    $email = $_POST['email'];
    $message = $_POST['email'];
    $antiSpam = $_POST['honey'];
    $regulation = $_POST['regulation'];
    $errors = Array();
	$return = Array();
    
    if (empty($antiSpam)) {
    
        ...
        
    } else {
        $return['status'] = 'ok';
    }
          
    header('Content-Type: application/json');
    echo json_encode($return);
}
Na zakończenie jeszcze mini formalność – tak by mieć czyste sumienie. Jeżeli chcesz zastosować jeszcze lepsze stylowanie, wtedy polecam przeczytać ten artykuł (a potem poszperać jeszcze na temat BEM)

Komentarze

  • Ubiegłeś mnie :(

    BTW nie sądzisz, że [novalidate] wypada dodać dopiero z poziomu skryptu JS? Inaczej w wypadku, gdy JS nie zadziała, całkowicie zostanie ominięta walidacja po stronie klienta.

  • Mimo, że temat formularza powtarzany był dziesiątki razy, Twój skrypt jest na prawdę święty i z chęcią go użyje w swoich projektach :)