Ошибка в скрипта обратной связи

Делаю форму обратной связи на сайт по видеоуроку, вроде все так же, как у автора, но форма не работает. Подозреваю, что не работает именно Js, так как браузер выдает ошибку, что обязательное поле пустое, хотя оно заполнено. Тем не менее, класс, который подсвечивает форму, если она не заполнена, навешивается корректно, т.е. валидация, как я понимаю, работает. Не могу понять, где ошибка, помогите, пожалуйста.

<div class="form">
                    <div class="main-font">Приглашаю тебя!</div>
                    <form action="#" id="form" class="form_body" method="POST">
                        <div class="input">
                            <input type="text" placeholder="Ваше Имя*" name="name" class="form_input _req">
                        </div>
                        <div class="input">
                            <textarea name="message" class="form_input" placeholder="Здесь можно оставить комметарий"></textarea>
                        </div>
                        <div class="button">
                            <button class="btn" id="btnForm">Я ПРИДУ</button>
                        </div>
                    </form>
document.addEventListener('DOMContentLoaded', function () {
const form = document.getElementById('form');
form.addEventListener('submit', formSend);

async function formSend(e) {
    e.preventDefault();

    let error = formValidate(form);

    let formData = new FormData(form);

    if (error === 0) {
         form.classList.add('_sending');

         let response = await fetch('sendmail.php', {
             method: 'POST',
             body: formData
         });
         if (response.ok) {
           let result  = await response.json();
           alert(result.message);
           form.reset();
           form.classList.remove('_sending');
         } else {
           alert('Ошибка');
           form.classList.remove('_sending');
         }
     } else {
        alert('Заполните обязательные поля');
     }
}

function formValidate(form) {
    let error = 0;
    const formReq = document.querySelector('._req');
    formRemoveError(formReq);

    if (formReq.value === '') {
        formAddError(formReq);
        error++;
    } 
} 

function formAddError(formReq) {
    formReq.parentElement.classList.add('_error');
    formReq.classList.add('_error');
}
function formRemoveError(formReq) {
    formReq.parentElement.classList.remove('_error');
    formReq.classList.remove('_error');
}
});
<?php
use PHPMailerPHPMailerPHPMailer;
use PHPMailerPHPMailerException;

require 'phpmailer/src/Exception.php';
require 'phpmailer/src/PHPMailer.php';

$mail = new PHPMailer(true);
$mail->CharSet = 'UTF-8';
$mail->setLanguage('ru', 'phpmailer/language/');
$mail->IsHTML(true);

$mail->setFrom('...');
$mail->addAddress('...');
$mail->Subject = '...';

$body = 'Ура! У тебя новый гость:';

if(trim(!empty($_POST['name']))){
    $body.='<p><strong>Имя:</strong> '.$_POST['name'].'</p>';
}
if(trim(!empty($_POST['message']))){
    $body.='<p><strong>Сообщение:</strong> '.$_POST['message'].'</p>';
}

$mail->Body = $body;

if(!$mail->send()) {
    $message = 'Ошибка';
} else {
    $message = 'Данные отправлены';
}

$response = ['message' => $message];

header('Content-type: application/json');
echo json_encode($response);
?>

На странице выводится две формы обратной связи. Первая работает корректно, вторая не работает. В чем может быть ошибка? И еще один момент: при нажатии на кнопку «отправить» скрипт уведомляет, что письмо отправлено. Но письмо на почту не поступает. Где-то есть ошибка? Подскажите, пожалуйста. Благодарю!

$(document).ready(function() {
$("#feedback_submit").click(function(){ 
     $.ajax({
        type: "POST",
        url:"sendmail.php",
        data:$("#callbacks").serialize(),
        error:function(){$("#erconts").html("Произошла ошибка!");},
		beforeSend: function() {
            $("#erconts").html("Отправляем данные...");
        },
		success: function(result){
			$("#erconts").html(result);
			checkThis();
		}
    });
    return false;
});
});
<form name="MyForm" action="" id="callbacks" class="feedback_form" method="post">
					<input type="text" name="name" data-placeholder="true" placeholder="Как к вам обращаться?" required>
					<input type="text" name="email" data-placeholder="true" placeholder="Ваш E-mail" required>
					<input type="submit" value="Отправить" id="feedback_submit">
				</form>
				<div id="erconts"></div>
        
<form name="MyForm" action="" id="callbacks" class="feedback_form" method="post">
					<input type="text" name="name" data-placeholder="true" placeholder="Как к вам обращаться?" required>
					<input type="text" name="email" data-placeholder="true" placeholder="Ваш E-mail" required>
					<input type="submit" value="Отправить" id="feedback_submit">
				</form>
				<div id="erconts"></div> 
<?php
if (isset($_POST["email"])){
if (isset($_POST["name"])) {$name = $_POST["name"];}
if (isset($_POST["email"])) {$email = $_POST["email"];}

if($name=="" or $email==""){
    echo "Заполните, пожалуйста, все поля!";
}else{
    $ip=$_SERVER["REMOTE_ADDR"];
$to = "pochta@mail.ru";
$subject = "Заявка!";
$headers .= "Content-Type: text/html; charset=UTF-8
";
$headers .= "From: Заявка с сайта";
$message = "
Имя: $name<br>
E-mail: $email<br><br>

--------------------------------------------------------<br>
---------------IP отправителя: $ip<br>
"; 
$send = mail($to, $subject, $message, $headers);


 if ($send == "true")
 {
 echo "Поздравляем! Ваша заявка принята!";
 }
 else
 {
 echo "Не удалось отправить, попробуйте снова!";
 }
}
}
?>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
<?php
 
# KCAPTCHA PROJECT VERSION 1.2.6
 
# Automatic test to tell computers and humans apart
 
# Copyright by Kruglov Sergei, 2006, 2007, 2008
# www.captcha.ru, www.kruglov.ru
 
# System requirements: PHP 4.0.6+ w/ GD
 
# KCAPTCHA is a free software. You can freely use it for building own site or software.
# If you use this software as a part of own sofware, you must leave copyright notices intact or add KCAPTCHA copyright notices to own.
# As a default configuration, KCAPTCHA has a small credits text at bottom of CAPTCHA image.
# You can remove it, but I would be pleased if you left it. ;)
 
# See kcaptcha_config.php for customization
 
class KCAPTCHA{
 
    // generates keystring and image
    function KCAPTCHA(){
 
        require(dirname(__FILE__).'/kcaptcha_config.php');
        $fonts=array();
        $fontsdir_absolute=dirname(__FILE__).'/'.$fontsdir;
        if ($handle = opendir($fontsdir_absolute)) {
            while (false !== ($file = readdir($handle))) {
                if (preg_match('/.png$/i', $file)) {
                    $fonts[]=$fontsdir_absolute.'/'.$file;
                }
            }
            closedir($handle);
        }   
    
        $alphabet_length=strlen($alphabet);
        
        do{
            // generating random keystring
            while(true){
                $this->keystring='';
                for($i=0;$i<$length;$i++){
                    $this->keystring.=$allowed_symbols{mt_rand(0,strlen($allowed_symbols)-1)};
                }
                if(!preg_match('/cp|cb|ck|c6|c9|rn|rm|mm|co|do|cl|db|qp|qb|dp|ww/', $this->keystring)) break;
            }
        
            $font_file=$fonts[mt_rand(0, count($fonts)-1)];
            $font=imagecreatefrompng($font_file);
            imagealphablending($font, true);
            $fontfile_width=imagesx($font);
            $fontfile_height=imagesy($font)-1;
            $font_metrics=array();
            $symbol=0;
            $reading_symbol=false;
 
            // loading font
            for($i=0;$i<$fontfile_width && $symbol<$alphabet_length;$i++){
                $transparent = (imagecolorat($font, $i, 0) >> 24) == 127;
 
                if(!$reading_symbol && !$transparent){
                    $font_metrics[$alphabet{$symbol}]=array('start'=>$i);
                    $reading_symbol=true;
                    continue;
                }
 
                if($reading_symbol && $transparent){
                    $font_metrics[$alphabet{$symbol}]['end']=$i;
                    $reading_symbol=false;
                    $symbol++;
                    continue;
                }
            }
 
            $img=imagecreatetruecolor($width, $height);
            imagealphablending($img, true);
            $white=imagecolorallocate($img, 255, 255, 255);
            $black=imagecolorallocate($img, 0, 0, 0);
 
            imagefilledrectangle($img, 0, 0, $width-1, $height-1, $white);
 
            // draw text
            $x=1;
            for($i=0;$i<$length;$i++){
                $m=$font_metrics[$this->keystring{$i}];
 
                $y=mt_rand(-$fluctuation_amplitude, $fluctuation_amplitude)+($height-$fontfile_height)/2+2;
 
                if($no_spaces){
                    $shift=0;
                    if($i>0){
                        $shift=10000;
                        for($sy=7;$sy<$fontfile_height-20;$sy+=1){
                            for($sx=$m['start']-1;$sx<$m['end'];$sx+=1){
                                $rgb=imagecolorat($font, $sx, $sy);
                                $opacity=$rgb>>24;
                                if($opacity<127){
                                    $left=$sx-$m['start']+$x;
                                    $py=$sy+$y;
                                    if($py>$height) break;
                                    for($px=min($left,$width-1);$px>$left-12 && $px>=0;$px-=1){
                                        $color=imagecolorat($img, $px, $py) & 0xff;
                                        if($color+$opacity<190){
                                            if($shift>$left-$px){
                                                $shift=$left-$px;
                                            }
                                            break;
                                        }
                                    }
                                    break;
                                }
                            }
                        }
                        if($shift==10000){
                            $shift=mt_rand(4,6);
                        }
 
                    }
                }else{
                    $shift=1;
                }
                imagecopy($img, $font, $x-$shift, $y, $m['start'], 1, $m['end']-$m['start'], $fontfile_height);
                $x+=$m['end']-$m['start']-$shift;
            }
        }while($x>=$width-10); // while not fit in canvas
 
        $center=$x/2;
 
        // credits. To remove, see configuration file
        $img2=imagecreatetruecolor($width, $height+($show_credits?12:0));
        $foreground=imagecolorallocate($img2, $foreground_color[0], $foreground_color[1], $foreground_color[2]);
        $background=imagecolorallocate($img2, $background_color[0], $background_color[1], $background_color[2]);
        imagefilledrectangle($img2, 0, 0, $width-1, $height-1, $background);        
        imagefilledrectangle($img2, 0, $height, $width-1, $height+12, $foreground);
        $credits=empty($credits)?$_SERVER['HTTP_HOST']:$credits;
        imagestring($img2, 2, $width/2-imagefontwidth(2)*strlen($credits)/2, $height-2, $credits, $background);
 
        // periods
        $rand1=mt_rand(750000,1200000)/10000000;
        $rand2=mt_rand(750000,1200000)/10000000;
        $rand3=mt_rand(750000,1200000)/10000000;
        $rand4=mt_rand(750000,1200000)/10000000;
        // phases
        $rand5=mt_rand(0,31415926)/10000000;
        $rand6=mt_rand(0,31415926)/10000000;
        $rand7=mt_rand(0,31415926)/10000000;
        $rand8=mt_rand(0,31415926)/10000000;
        // amplitudes
        $rand9=mt_rand(330,420)/110;
        $rand10=mt_rand(330,450)/110;
 
        //wave distortion
 
        for($x=0;$x<$width;$x++){
            for($y=0;$y<$height;$y++){
                $sx=$x+(sin($x*$rand1+$rand5)+sin($y*$rand3+$rand6))*$rand9-$width/2+$center+1;
                $sy=$y+(sin($x*$rand2+$rand7)+sin($y*$rand4+$rand8))*$rand10;
 
                if($sx<0 || $sy<0 || $sx>=$width-1 || $sy>=$height-1){
                    continue;
                }else{
                    $color=imagecolorat($img, $sx, $sy) & 0xFF;
                    $color_x=imagecolorat($img, $sx+1, $sy) & 0xFF;
                    $color_y=imagecolorat($img, $sx, $sy+1) & 0xFF;
                    $color_xy=imagecolorat($img, $sx+1, $sy+1) & 0xFF;
                }
 
                if($color==255 && $color_x==255 && $color_y==255 && $color_xy==255){
                    continue;
                }else if($color==0 && $color_x==0 && $color_y==0 && $color_xy==0){
                    $newred=$foreground_color[0];
                    $newgreen=$foreground_color[1];
                    $newblue=$foreground_color[2];
                }else{
                    $frsx=$sx-floor($sx);
                    $frsy=$sy-floor($sy);
                    $frsx1=1-$frsx;
                    $frsy1=1-$frsy;
 
                    $newcolor=(
                        $color*$frsx1*$frsy1+
                        $color_x*$frsx*$frsy1+
                        $color_y*$frsx1*$frsy+
                        $color_xy*$frsx*$frsy);
 
                    if($newcolor>255) $newcolor=255;
                    $newcolor=$newcolor/255;
                    $newcolor0=1-$newcolor;
 
                    $newred=$newcolor0*$foreground_color[0]+$newcolor*$background_color[0];
                    $newgreen=$newcolor0*$foreground_color[1]+$newcolor*$background_color[1];
                    $newblue=$newcolor0*$foreground_color[2]+$newcolor*$background_color[2];
                }
 
                imagesetpixel($img2, $x, $y, imagecolorallocate($img2, $newred, $newgreen, $newblue));
            }
        }
        
        header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); 
        header('Cache-Control: no-store, no-cache, must-revalidate'); 
        header('Cache-Control: post-check=0, pre-check=0', FALSE); 
        header('Pragma: no-cache');
        
        if(function_exists("imagejpeg")){
            header("Content-Type: image/jpeg");
            imagejpeg($img2, null, $jpeg_quality);
        }else if(function_exists("imagegif")){
            header("Content-Type: image/gif");
            imagegif($img2);
        }else if(function_exists("imagepng")){
            header("Content-Type: image/x-png");
            imagepng($img2);
        }
    }
 
    // returns keystring
    function getKeyString(){
        return $this->keystring;
    }
}
 
?>

В этой статье мы изучим, как добавить на сайт форму обратной связи и настроить её под свои поля. Отправляет данные эта форма на почту. Её код написан на чистом CSS, JavaScript и PHP.

Назначение и основные характеристики

Форма обратной связи (на английском feedback или contact form) – это один из способов взаимодействия клиента с менеджером или владельцем сайта. Например, её можно использовать для получения отзывов от клиентов, заказа услуг, оставления заявок и т.д.

При желании скрипт формы, представленный здесь, можно изменить и использовать для решения других различных задач на сайте. Например, для отправки комментариев, авторизации, добавление товаров в корзину и т.д.

Основные характеристики этой формы:

  • работа без перезагрузки страницы (через AJAX);
  • наличие графической капчи для защиты от спама;
  • возможность добавления файлов;
  • валидация данных на стороне клиента (в браузере) и на сервере;
  • отправка успешных форм на почту (файлы могут приходить как в виде вложений, так и в виде ссылок).

Исходные коды

Файлы формы обратной связи расположены на GitHub: https://github.com/itchief/feedback-form.

Последний релиз – 4.0.6. Скачать его можно, нажав на эту ссылку. Распространяется она под лицензией MIT.

Оформление формы выполнено с помощью стилей, находящихся в файле «form-processing.css». Для исполнения серверных сценариев необходимо иметь PHP не ниже v7.0.

Другие архивы различных форм, созданных на основе этой, можете посмотреть на «Яндекс.Диск».

Демо формы

Скриншоты

Внешний вид формы обратной связи
Как осуществляется валидация формы обратной связи
Отображение информации об успешной отправки формы обратной связи

Установка и настройка

1. Добавить форму в HTML документ.

<form id="form" action="/feedback/processing.php" enctype="multipart/form-data" novalidate>
  ...
</form>

Указание обработчика осуществляется с помощью атрибута action. Форма используется для получения сведений от пользователя. Пример формы расположен в «index.html».

2. Подключить к странице CSS и JavaScript файлы:

<link rel="stylesheet" href="/feedback/css/form-processing.css">
<script src="/feedback/js/form-processing.js"></script>

JavaScript используется для отправки формы на сервер через AJAX (без перезагрузки страницы), а также для проверки вводимых пользователем данных и отображения сообщения об успешном завершении.

3. Написать код для вывода сообщения об успехе.

Какое сообщение и каким образом оно должно выводиться пользователю при успешной отправке формы вы определяете сами. Выполнять это следует в обработчике события success, которое генерируется в «form-processing.js» для тега <form>.

Например, для этого в «index.html» используется следующий HTML и JavaScript.

HTML код:

<!-- Сообщение об успешной отправки формы -->
<div class="form-success form-success_hide">
  <div class="form-success__message">Форма успешно отправлена. Нажмите <button type="button" class="form-success__btn">здесь</button>, если нужно отправить ещё одну форму.</div>
</div>

JavaScript:

document.addEventListener('itc.successSendForm', (e) => {
  const el = e.target.closest('.form-container').querySelector('.form-success');
  el.classList.remove('form-success_hide');
});
// при клике на .form-success__btn
document.querySelector('.form-success__btn').addEventListener('click', (e) => {
  const el = e.target.closest('.form-container').querySelector('form');
  const form = ItcSubmitForm.getOrCreateInstance(el);
  form.reset();
  e.target.closest('.form-container').querySelector('.form-success').classList.add('form-success_hide');
});

Метод reset() используется для сброса формы.

4. Инициализировать форму как ItcSubmitForm:

// 'form' - селектор для выбора <form>
ItcSubmitForm.getOrCreateInstance('form');

Передача дополнительных настроек осуществляется в формате объекта, который нужно указать в качестве 2 аргумента:

ItcSubmitForm.getOrCreateInstance('form', {
  isCheckValidationOnClient: true, // проверять форму перед отправкой на сервер
  attachMaxItems: 5, // максимальное количество файлов, которые можно добавить к форме
  attachMaxFileSize: 512, // 512 Кбайт - максимальный размер файла
  attachExt: ['jpg', 'jpeg', 'bmp', 'gif', 'png'] // допустимые расширения файлов
});

В примере указаны значения ключей, которые они имеют по умолчанию. При необходимости установите им другие значения.

5. Настроить константы в серверном php-скрипте «form-processing.php».

5.1. Если вы используете капчу, встроенную в форму, то константе HAS_CHECK_CAPTCHA необходимо установить значение true. В противном случае false:

define('HAS_CHECK_CAPTCHA', true);

5.2. Файлы (поле attach):

// не пропускать форму, если к ней не прикреплён хотя бы один файл
const HAS_ATTACH_REQUIRED = false;
// разрешённые mime типы файлов
const ALLOWED_MIME_TYPES = ['image/jpeg', 'image/gif', 'image/png'];
// максимальный размер файла
const MAX_FILE_SIZE = 512 * 1024;

5.3. Настройки почты:

// отправлять письмо на указанный адрес email
const HAS_SEND_EMAIL = true;
// добавить файлы в тело письма в виде ссылок (В противном случае прикрепить)
const HAS_ATTACH_IN_BODY = true;
// базовый URL-адрес (используется, если составления полного URL для ссылок, добавляемых в тело письма)
const BASE_URL = 'https://domain.com';
// настройка почты (отправка осуществляется через SMTP)
const EMAIL_SETTINGS = [
  'addresses' => ['manager@domain.com'], // кому необходимо отправить письмо
  'from' => ['no-reply@domain.com', 'Имя сайта'], // от какого email и имени необходимо отправить письмо
  'subject' => 'Сообщение с формы обратной связи', // тема письма
  'host' => 'ssl://smtp.yandex.ru', // SMTP-хост
  'username' => 'name@yandex.ru', // // SMTP-пользователь
  'password' => '*********', // SMTP-пароль
  'port' => '465' // SMTP-порт
];

5.4. Отправка уведомления пользователю (необходим email в форме):

// необходимо ли отправлять уведомление пользователю на почту
const HAS_SEND_NOTIFICATION = false;
// тема письма
const SUBJECT_FOR_CLIENT = 'Ваше сообщение доставлено';

5.5. HAS_WRITE_LOG определяет, необходимо ли писать предупреждения и ошибки при обработке формы в лог (файлы записываются в папку /feedback/logs/):

// писать предупреждения и ошибки в лог
define('HAS_WRITE_LOG', true);

5.6. Константа HAS_WRITE_TXT определяет необходимо ли сохранять успешные формы в файл «/feedback/logs/forms.log»:

// записывать успешные формы в файл forms.log
const HAS_WRITE_LOG = true;

6. После завершения настройки, скопировать папку «feedback» в корневую директорию сайта.

По умолчанию в папке «feedback» имеется файл «index.html». Его можно использовать для тестирования формы перед тем как создавать свои на нужных страницах.

На сайте с доменным именем «domain.com» эта форма будет доступна по следующему URL: http://domain.com/feedback/ (или https://domain.com/feedback/).

Часто задаваемые вопросы

1. Как убрать капчу

Если вам не нужна встроенная капча, то необходимо выполнить 2 действия.

1. Удалить из HTML блок с капчей:

<!-- Капча -->
<div class="form-group form-captcha">
  ...
</div>

2. В php обработчике формы установить константе HAS_CHECK_CAPTCHA значение false.

// проверять ли капчу
  const HAS_CHECK_CAPTCHA = false;

2. Как добавить новое поле в форму

Для добавления нового поля (например, phone) достаточно выполнить следующие действия.

1. Вставить HTML код, содержащий <input type="tel" name="phone"> в <form>:

<!-- Телефон -->
<div class="form-group">
  <label for="phone" class="control-label">Телефон</label>
  <div class="input-group">
    <div class="input-group-prepend">
      <div class="input-group-text">+7</div>
    </div>
    <input id="phone" type="tel" name="phone" class="form-control" value="(___)___-__-__" pattern="^(?[0-9]{3})?(s+)?[0-9]{3}-?[0-9]{2}-?[0-9]{2}$">
    <div class="invalid-feedback"></div>
  </div>
</div>

2. При необходимости можно создать маску для телефона. Например, на базе «masked_input_1.4.1-min.js»:

<script>
// masked_input_1.4.1-min.js
// angelwatt.com/coding/masked_input.php

MaskedInput({
  elm: document.getElementById('phone'), // select by id
  format: '(___)___-__-__',
  separator: '()-'
});
</script>

3. Добавить в «form-processing.php» код для валидации номера телефона:

// валидация phone
if (!empty($_POST['phone'])) {
  $data['form']['phone'] = preg_replace('/D/', '', $_POST['phone']);
  if (!preg_match('/^(8|7)(d{10})$/', $phone)) {
    $data['result'] = 'error';
    $data['errors']['phone'] = 'Поле содержит не корректный номер.';
    itc_log('Phone не корректный.');
  }
}

Также необходимо добавить строчку, которая будет заменять плейсхолдер %phone% в шаблоне письма:

$search = ['%subject%', '%name%', '%email%', '%message%', '%phone%', '%date%'];
$replace = [EMAIL_SETTINGS['subject'], $data['form']['name'], $data['form']['email'], $data['form']['message'], $data['form']['phone'], date('d.m.Y H:i')];

В месте, где записываем лог:


$output .= 'Телефон: ' . isset($data['form']['phone']) ? $data['form']['phone'] : 'не указан' . PHP_EOL;

4. Добавить в шаблон письма «email.tpl»:

Телефон: <b>%phone%</b>

Пример формы, в котором присутствует поле для ввода телефона: скачать.

3. Как использовать почту Gmail

По умолчанию в аккаунте Google отключена возможность отправлять почту через SMTP-сервер Gmail с помощью PHP. Чтобы её включить необходимо предоставить доступ к аккаунту для приложения.

Но перед этим, необходимо узнать текущее состояние двухэтапной аутентификации.

Для этого необходимо перейти в Google аккаунт и открыть раздел «Безопасность». В группе «Вход в аккаунт Google» найти настройку «Двухэтапная аутентификация» и посмотреть её статус.

Если настройка «Двухэтапная аутентификация» выключена, то перейти к группе «Ненадежные приложения, у которых есть доступ к аккаунту» и нажать на «Открыть доступ (не рекомендуется)». После этого на открывшейся странице перевести переключатель «Небезопасные приложения заблокированы» в состояние включено. На этом действия по настройке Google аккаунта завершены.

В противном случае, т.е. когда настройка «Двухэтапная аутентификация» включена необходимо выполнить другие действия, а именно создать пароль для приложения. Т.к. использовать пароль, который вы используете для обычного входа на почту, для SMTP аутентификации в соответствии с безопасностью Google в этом случае нельзя.

Для создания паролю приложению нужно перейти в Google аккаунт, а затем в раздел «Безопасность». Далее на этой странице найти группу настроек «Вход в аккаунт Google» и кликнуть на ссылку «Пароли приложений». На открывшейся странице из раскрывающегося списка «Приложение» необходимо выбрать «Другое (введите название)» и написать, например, имя своего сайта, а затем нажать на кнопку «Создать». Созданный пароль необходимо скопировать, он нам нужен будет при настройке отправки почты с использованием PHPMailer.

Пароли приложений в Google аккаунте

Для этого необходимо открыть файл «form-processing.php» и сделать следующие настройки почты:

const EMAIL_SETTINGS = [
  'addresses' => ['manager@domain.com'], // кому необходимо отправить письмо
  'from' => ['name@gmail.com', 'Имя'], // от какого email и имени необходимо отправить письмо
  'subject' => 'Сообщение с формы обратной связи', // тема письма
  'host' => 'ssl://smtp.gmail.com', // SMTP-хост
  'username' => 'name@gmail.com', // // SMTP-пользователь
  'password' => '*********', // SMTP-пароль
  'port' => '465' // SMTP-порт
];

При включенной двухэтапной аутентификации Google мы указываем в качества ключа password пароль, созданный для приложения. В противном случае – пароль от аккаунта.

Нескольких форм на одной странице

Для установки нескольких форм на страницу необходимо выполнить следующие действия.

1. Добавить к каждой из них id (например, form-1, form-2 и т.д.), для того их можно было более просто получить.

2. Если их обработка на сервере будет значительно отличаться, то создать необходимые php-скрипты и указать их в action:

<!-- форма 1 -->
<form id="form-1" action="/feedback/form-processing-1.php" enctype="multipart/form-data" novalidate>
  ...
</form>
<!-- форма 2 -->
<form id="form-2" action="/feedback/form-processing-2.php" enctype="multipart/form-data" novalidate>
  ...
</form>

3. Добавить в URL адрес для капчи GET-параметр id (так образом мы можем создать свою капчу для каждой формы):

<!-- форма 1 -->
<form id="form-1" action="/feedback/form-processing-1.php" enctype="multipart/form-data" novalidate>
  ...
  <img class="form-captcha__image" src="/feedback/captcha/captcha.php?id=captcha-1" data-src="/feedback/captcha/captcha.php?id=captcha-1" width="132" height="46" alt="Капча">
  ...
</form>
<!-- форма 2 -->
<form id="form-2" action="/feedback/form-processing-2.php" enctype="multipart/form-data" novalidate>
  ...
  <img class="form-captcha__image" src="/feedback/captcha/captcha.php?id=captcha-2" data-src="/feedback/captcha/captcha.php?id=captcha-2" width="132" height="46" alt="Капча">
  ...
</form>

4. Выполнить инициализацию форм как ItcSubmitForm:

// инициализация #form-1
ItcSubmitForm.getOrCreateInstance('#form-1');
// инициализация #form-2
ItcSubmitForm.getOrCreateInstance('#form-2');

5. Внести изменения в «form-processing-1.php» и «form-processing-2.php» для обработки форм. Изначально эти файлы можно создать как копии «form-processing.php».

6. В месте, в котором проверяется код капчи, установить в качестве ключа $_SESSION то значение id, которое мы использовали в <img>:

// в файле form-processing-1.php
if ($_POST['captcha'] === $_SESSION['captcha-1']) {

// в файле form-processing-2.php
if ($_POST['captcha'] === $_SESSION['captcha-2']) {

Пример с двумя формами, расположенными на одной странице, расположен на GitHub в папке examples.

Что внутри?

Форма в HTML состоит из следующих элементов:

  • <input type="text" name="name"> – имя;
  • <input type="email" name="email"> – email;
  • <textarea name="message"> – сообщение;
  • <input type="file" name="attach[]" multiple> – файлы;
  • <input type="text" name="captcha"> – капча;
  • <input type="checkbox" name="agree"> – пользовательское соглашение;
  • <button type="submit"> – кнопка для отправки формы.

Это набор полей, которая <form> имеет по умолчанию. При необходимости в неё можно добавить новые, а также удалить существующие.

Работа формы выполняется через AJAX.

Как работает AJAX форма обратной связи

Когда пользователь нажимает на кнопку «Отправить», возникает событие submit на элементе <form>. В JavaScript срабатывает соответствующий обработчик. Код этого обработчика выполняет валидацию полей (если значение ключа _isCheckValidationOnClient равно true). Если все поля соответствуют требованиям, то форма отправляется на сервер через AJAX (сбор данных осуществляется с использованием FormData).

Запрос на сервере обрабатывается в «form-processing.php». Сначала в нём осуществляется валидация данных. При их корректности осуществляется отправка формы на указанный в настройках email. Результат обработки формы отправляется клиенту в формате JSON.

После получения ответа от сервера, JavaScript разбирает его и определённым образом обновляем страницу.

Как организована валидация

Валидация полей формы в JavaScript выполняется с использованием метода checkValidity():

let valid = true;
// input, textarea
this._elForm.querySelectorAll('input, textarea').forEach(el => {
  if (el.checkValidity()) {
    this._setStateValidaion(el, 'success');
  } else {
    this._setStateValidaion(el, 'error', el.validationMessage);
    valid = false;
  }
});
return valid;

Метод _setStateValidaion() устанавливает для элемента определённые классы, которые используются для его стилизации.

Как генерируется капча

Для генерации капчи используются 3 файла:

  • captcha.php (скрипт для генерации капчи);
  • oswald.ttf (шрифт, посредством которого код капчи пишется на изображении);
  • background.png (изображение, на которое накладывается текст капчи).

При нажатии на кнопку Обновить генерируется новый код капчи и обновляется его изображение на странице.

Добавление файлов в форму

В <form> код для добавления файлов организован с использованием <input>, который имеет атрибуты type="file" и multiple:

<!-- Файлы -->
<div class="form-group form-attach" data-count="5">
  <div class="form-attach__label">Файлы (не более <span class="form-attach__count">5</span>)</div>
  <div class="form-attach__wrapper">
    <input type="file" name="attach[]" multiple required>
    <div class="form-attach__description">
      <div>Нажмите для загрузки файлов или перетащите их</div>
      <div class="text-sm">PNG, JPG, GIF (до 512 Кбайт)</div>
    </div>
    <div class="form-attach__items"></div>
  </div>
  <div class="invalid-feedback"></div>
</div>

Для создания превью изображений в JavaScript используется FileReader.

Файлы формы обратной связи имеют кодировку UTF-8 без BOM. Для проверки работоспособности формы в Денвере необходимо в корне сайта создать файл .htaccess и добавить в него строчку: AddDefaultCharset UTF-8.

Что отправляет form-processing.php на клиент (в браузер)

В «form-processing.php» отправляется всегда то, что находится в переменной $data.

Результат обработки находится в ключе result. Он может иметь одно из следующих значений:

  • 'success' — успех;
  • 'error' — при обработке формы возникли ошибки.

Ошибки валидации помещаются в $data['errors']. Например так записывается ошибка для поля email:

$data['errors']['email'] = 'Email не корректный.';

Добавления сообщений, которые затем нужно вывести в консоль браузера, осуществляется так:

$data['logs'][] = 'Сообщение, которое нужно вывести в консоль браузера.';

Добавление файлов к письму

По умолчанию файлы прикрепляются к письму. Осуществляется это так:

foreach ($attachs as $attach) {
  $mail->addAttachment($attach);
}

Кроме этого имеется также возможность добавить их в виде ссылок в тело письма (зависит это от значения константы HAS_ATTACH_IN_BODY)

$ul = 'Файлы, прикреплённые к форме: <ul>';
foreach ($attachs as $attach) {
  $href = str_replace($_SERVER['DOCUMENT_ROOT'], '', $attach);
  $name = basename($href);
  $ul .= '<li><a href="' . BASE_URL . $href . '">' . $name . '</a></li>';
  $data['href'][] = BASE_URL . $href;
}
$ul .= '</ul>';

В письмо «email.tpl» они вставляются в место, определяемым плейсхолдером %attachs%.

Другие статьи по созданию форм обратных связей:

  • Форма обратной связи с использованием Bootstrap
  • Форма обратной связи в модальном окне
  • Добавление Google reCAPTCHA к PHP форме
  • Защита PHP формы от спама с помощью invisible reCAPTCHA

Валидация формы обратной связи.

Вступление.

На многих сайтах присутствует страница с формой обратной связи, элемент достаточно нужный и с этим трудно не согласиться. Давайте вместе создадим такую форму, позволим пользователю ввести в неё данные, проверим их на корректность и после этого отправим данные на сервер.

Прежде чем приступать работе, нужно ясно понять:
— что из себя будет представлять форма обратной связи;
— какие поля будет в себя включать;
— какие данные мы будем проверять и по какому критерию;
— какой лучше использовать метод и способ отправки данных формы на сервер.

Немного подумав, мы решили, что форма будет иметь следующие поля:

  1. имя — ведь нам нужно как-то обращаться к пользователю, когда мы будем писать ему ответ;
  2. электронная почта — мы же должны знать, куда отправлять ответ;
  3. тема сообщения — тоже необходимая информация, по ней мы определимся, кто будет отвечать на это письмо;
  4. текст сообщения — здесь комментарии излишни.

Сразу определимся, что все поля обязательны для заполнения и, соответственно, все они требуют проверки на корректность введённых данных.

Мы не будем подробно рассматривать этапы создания HTML вёрстки и оформления её стилями, т.к. наша главная задача — написать скрипт валидации введённых данных и отправить их на сервер. Поэтому сразу представим готовую разметку HTML и таблицу стилей для него с минимумом комментариев.

HTML-разметка формы обратной связи.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

<form name=«feedback» method=«POST»>

<ul class=«form-list»>

<li>

<label>Ваше имя:</label>

<div>

<input class=«form-control» type=«text» name=«username» maxlength=«30» value=«»>

</div>

<span class=«error»></span>

</li>

<li>

<label>Электронная почта:</label>

<div>

<input class=«form-control» type=«text» name=«usermail» maxlength=«50» value=«»>

</div>

<span class=«error»></span>

</li>

<li>

<label>Тема сообщения:</label>

<div>

<input class=«form-control» type=«text» name=«subject» maxlength=«50» value=«»>

</div>

<span class=«error»></span>

</li>

<li>

<label>Текст сообщения:</label>

<div>

<textarea class=«form-control» name=«textmess»></textarea>

</div>

<span class=«error»></span>

</li>

<li>

<label></label>

<div>

<button id=«send_mess» type=«submit»>отправить сообщение</button>

</div>

</li>

</ul>

</form>

Отметим лишь один элемент данной формы:

<span class=«error»></span>

В этот <span> мы будем выводить красиво оформленное сообщение об ошибке.

Таблица стилей для формы обратной связи.

Представлены только стили, относящиеся непосредственно к самой форме обратной связи.

Очень важно.
Для уменьшения размера таблицы стилей, я не буду приводить свойства с вендорными префиксами, обеспечивающими кроссбраузерность. Не забывайте прописывать их при реализации своих проектов.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

.form-list { max-width: 800px; }

.form-list li {

display: flex;

position: relative;

}

.form-list li + li { margin-top: 24px; }

.form-list label {

width: 192px;

flex-shrink: 0;

text-align: right;

padding: 10px 24px 0 0;

}

.form-list label + div { width: calc(100% 192px); }

.form-list button {

height: 36px;

font-size: 12px;

line-height: 38px;

color: #fff;

text-align: center;

text-transform: uppercase;

padding: 0 24px;

border: none;

border-radius: 5px;

background: #058cd0;

cursor: pointer;

transition: all 0.2s;

}

.form-list button:hover { background: #0497e0; }

.form-control {

width: 100%;

height: 36px;

font-size: 15px;

line-height: 36px;

padding: 0 10px;

border: solid 1px #c6c6c6;

border-radius: 3px;

background: #fff;

}

.form-control:focus {

color: #3c464e;

border: solid 1px #82b0d5;

box-shadow: 0 0 9px rgba(23, 198, 254, .37), inset 0 1px 2px rgba(91, 114, 132, .3);

}

input[type=»text»].form-control { max-width: 400px; }

textarea.form-control {

max-width: 600px;

height: 156px;

display: block;

font: normal 15px/20px Roboto, Verdana, sans-serif;

resize: none;

}

.error {

max-width: 260px;

display: none;

font-size: 13px;

line-height: 15px;

color: #fff;

position: absolute;

left: 216px;

top: calc(100% + 8px);

z-index: 100;

padding: 6px 10px 7px;

border-radius: 6px;

background: #d24a4a;

}

.error:before {

width: 0;

height: 0;

content: »;

position: absolute;

left: 15px;

top: -7px;

border-right: 8px solid transparent;

border-left: 8px solid transparent;

border-bottom: 8px solid #d24a4a;

}

.form-control_error { border-color: #d24a4a; }

Критерии валидации формы обратной связи.

Прежде чем мы начнём писать скрипт валидации, давайте определимся, по каким критериям мы будем оценивать корректность введённых пользователем данных, какие данные мы будем считать валидными, а какие нет.

Во-первых, для всех полей формы проверяем заполнены они или нет. Это самая простая проверка.

Это очень важно.
Для следующих проверок будут использоваться регулярные выражения. Мы не будем разбирать, как составляются и работают регулярные выражения. Это отдельная и достаточно объёмная тема для разговора. О регулярных выражениях вы сможете прочитать

здесь

.
При написании скрипта валидации мы будем использовать уже готовые паттерны (шаблоны) RegExp.

Во-вторых, поле «Ваше имя». Давайте определимся, что мы ожидаем получить реальное имя человека на русском языке. Значит поступившие данные должны содержать только прописные и строчные буквы русского алфавита. Разрешим ещё и пробел — пользователь кроме имени может указать ещё и своё отчество. Наличие любых других символов приведёт к ошибке.
Паттерн RegExp для такой проверки будет выглядеть так: /^[а-яёА-ЯЁs]+$/.

В-третьих, поле «Электронная почта». Здесь особых объяснений не нужно.
Паттерн RegExp для этого поля выглядит так:
/^[A-Za-z0-9](([_.-]?[a-zA-Z0-9]+)*)@([A-Za-z0-9]+)(([.-]?[a-zA-Z0-9]+)*).([A-Za-z])+$/.

В-четвёртых, поля «Тема сообщения» и «Текст сообщения». В этих полях допустимы любые символы. Более серьёзная проверка на спам и защита от хакеров осуществляется в php-скрипте, в который мы передадим данные формы, но это находится за пределами темы нашей статьи.

Для ограничения области видимости нашего скрипта и исключения конфликтов с другими js-скриптами, разместим код в анонимной самозапускающейся функции.

;(function() {

‘use strict’;

})();

При написании JS-скрипта мы будем использовать конструкцию Class. Это позволит создать несколько экземпляров формы на одной странице. Например, форма заказа товара и форма заказа обратного звонка. Такое очень часто встречается в лендингах.

Создание экземпляров формы обратной связи.

В-первую очередь, создадим коллекцию всех форм, которые расположены на странице. Далее, с помощью метода for...of переберём полученную коллекцию, при этом создадим экземпляр текущей формы, используя конструктор класса Form.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

;(function() {

‘use strict’;

class Form {

constructor(form) {

}

}

// коллекция всех HTML форм на странице

const forms = document.querySelectorAll(‘[name=feedback]’);

// если формы на странице отсутствуют, то прекращаем работу функции

if (!forms) return;

// перебираем полученную коллекцию элементов

for (let form of forms) {

// создаём экземпляр формы

const f = new Form(form);

}

})();

Весь дальнейший JS-код мы будем писать внутри конструкции class Form { ... }.

Прежде всего рассмотрим конструктор класса Form. Конструктор инициализирует ряд объектов и переменных, содержащих информацию об экземпляре галереи. В качестве аргумента конструктор принимает объект формы обратной связи, экземпляр которого создаётся в данный момент.

class Form {

constructor(form) {

this.form = form;

// коллекция полей формы из которой мы будем извлекать данные

this.fields = this.form.querySelectorAll(‘.form-control’);

// объект кнопки, на который повесим обработчик события начала валидации формы

// и отправки её значений на сервер

this.btn = this.form.querySelector(‘[type=submit]’);

// флаг ошибки валидации

this.iserror = false;

// регистрируем обработчики событий

this.registerEventsHandler();

}

}

Кроме проинициализированных переменных, нам необходимо добавить ряд статических переменных, в которых будет храниться паттерны RegExp и массив сообщений об ошибке.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

class Form {

// паттерны RegExp о которых было написано выше

static patternName = /^[а-яёА-ЯЁs]+$/;

static patternMail = /^[AZaz09](([_.]?[azAZ09]+)*)@([AZaz09]+)(([.]?[azAZ09]+)*).([AZaz])+$/;

// массив с сообщениями об ошибке

// эти сообщения можно разместить и внутри кода валидации, но лучше,

// если они будут находиться в одном месте

// это облегчит их редактирование, а так же проще будет прописать новые,

// если решите добавить критерии валидации

static errorMess = [

‘Незаполненное поле ввода’, // 0

‘Введите Ваше реальное имя’, // 1

‘Укажите Вашу электронную почту’, // 2

‘Неверный формат электронной почты’, // 3

‘Укажите тему сообщения’, // 4

‘Напишите текст сообщения’ // 5

];

constructor(form) {

this.form = form;

// коллекция полей формы из которой мы будем извлекать данные

this.fields = this.form.querySelectorAll(‘.form-control’);

// объект кнопки, на который повесим обработчик события начала валидации формы

// и отправки её значений на сервер

this.btn = this.form.querySelector(‘[type=submit]’);

// флаг ошибки валидации

this.iserror = false;

// регистрируем обработчики событий

this.registerEventsHandler();

}

}

Конструктор, кроме инициализации объектов и переменных, вызывает функцию registerEventsHandler, в которой регистрируются все обработчики событий.

registerEventsHandler() {

// запуск валидации при отправке формы

this.btn.addEventListener(‘click’, this.validForm.bind(this));

}

На данный момент мы зарегистрируем только один обработчик, который фиксирует событие click на кнопке отправки данных формы обратной связи. При наступлении события вызывается функция validForm, запускающая валидацию формы.

Запуск валидации формы обратной связи.

Прежде чем переходить к валидации данных, нужно эти данные получить. Для этой цели будем использовать объект FormData, который автоматически считает поля формы и их значения. Объект содержит ряд методов, которые мы будем использовать в дальнейшем:

formData.keys()
Возвращает iterator, который позволяет пройтись по всем ключам, содержащимся внутри обьекта formData.
formData.get()
Возвращает первое значение ассоциированное с переданным ключом из объекта formData.
formData.append()
Создаёт новое поле объекта formData и присваивает ему значение.

validForm(e) {

// отменяем действие браузера по умолчания при клике по

// кнопке формы <button type=»submit»>, чтобы не происходило обновление страницы

e.preventDefault();

// объект представляющий данные HTML формы

const formData = new FormData(this.form);

}

Итак, объект со значениями полей формы обратной связи в формате 'property': 'value' получены. И что же нам с ними делать дальше?
Чтобы перебрать свойства объекта, воспользуемся циклом for...of. Этот цикл последовательно переберёт свойства property и вызовет функцию getError, которая будет сравнивать значение свойства с паттерном RegExp и возвращать результат сравнения в виде пустой строки или текста ошибки валидации.

Посмотрим, как теперь выглядит функция validForm:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

validForm(e) {

// отменяем действие браузера по умолчания при клике по

// кнопке формы <button type=»submit»>, чтобы не происходило обновление страницы

e.preventDefault();

// объект представляющий данные HTML формы

const formData = new FormData(this.form);

// объявим переменную error, в которую будем записывать текст ошибки

let error;

// перебираем свойства объекта с данными формы

for (let property of formData.keys()) {

// вызываем функцию, которая будет сравнивать

// значение свойства с паттерном RegExp и возвращать результат

// сравнения в виде пустой строки или текста ошибки валидации

error = this.getError(formData, property);

}

}

Валидация введёных значений формы обратной связи.

Теперь мы знаем как получить имя свойства объекта, которое равно имени поля формы, знаем как получить значение этого свойства. Теперь нужно решить, каким способом мы будем назначать полученному свойству соответствующий ему паттерн RegExp для проверки корректности его значения.
Обычно это делается с помощью конструкций if ... else или switch.
Но есть ещё один способ, который заменяет эти конструкции — использование объектного литерала. Мы сэкономим несколько строчек кода, при сохранении функциональности. Давайте и остановимся на этом способе.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

getError(formData, property) {

let error = »;

// создаём литеральный объект validate

// каждому свойству литерального объекта соответствует анонимная функция, в которой

// длина значения поля, у которого атрибут ‘name’ равен ‘property’, сравнивается с 0,

// а само значение — с соответствующим паттерном

// если сравнение истинно, то переменной error присваивается текст ошибки

const validate = {

username: () => {

if (formData.get(‘username’).length == 0 || Form.patternName.test(formData.get(‘username’)) == false) {

error = Form.errorMess[1];

}

},

usermail: () => {

if (formData.get(‘usermail’).length == 0) {

error = Form.errorMess[2];

} else if (Form.patternMail.test(formData.get(‘usermail’)) == false) {

error = Form.errorMess[3];

}

},

subject: () => {

if (formData.get(‘subject’).length == 0) {

error = Form.errorMess[4];

}

},

textmess: () => {

if (formData.get(‘textmess’).length == 0) {

error = Form.errorMess[5];

}

}

}

// если после вызова анонимной функции validate[property]() переменной error

// было присвоено какое-то значение, то это значение и возвращаем,

// в противном случае значение error не изменится

validate[property]();

return error;

}

Валидация шаг за шагом

Давайте, шаг за шагом, рассмотрим, как работает функция getError на примере валидации поля ‘usermail’.

  • 1

    Сначала мы создаём литеральный объект validate, у которого значением свойства является анонимная функция.

    var validate = {

    usermail: () => {

    if (formData.get(‘usermail’).length == 0) {

    error = Form.errorMess[2];

    } else if (Form.patternMail.test(formData.get(‘usermail’)) == false) {

    error = Form.errorMess[3];

    }

    }

    }

  • 2

    Затем мы вызываем анонимную функцию, указывая свойство, значением которого эта функция является.

  • 3

    В теле функции мы осуществляем проверку с использованием объекта formData, в котором находятся значения всех полей формы обратной связи. В нашем случае это formData.get('usermail').
    Сначала мы сравниваем длину formData.get('usermail') с 0.

    if (formData.get(‘usermail’).length == 0) { ... }

    Затем сравниваем formData.get('usermail') с шаблоном RegExp patternMail

    else if (patternMail.test(formData.get(‘usermail’)) == false) { ... }

  • 4

    Если условие сравнения окажется истинным, то присваиваем переменной error соответствующее значение из массива ошибок: errorMess[2] или errorMess[3]
    Если оба условия сравнения ложны, то переменной error ничего не присваивается.

  • 5

    Возвращаем значение переменной error, которое является или строкой с текстом ошибки или пустой строкой.

Обработка переменной error.

Опять возвращаемся к функции validForm. После вызова функции getError мы получили значение переменной error. А что с ней будем делать дальше? А это зависит от того, что в себе содержит эта переменная: строку с текстом ошибки или пустую строку.
Начнём с того, что сравним длину строки, содержащейся в переменной error с 0. Если результат проверки не будет равен 0, значит в error находится текст ошибки и нужно показать эту ошибку пользователю. Для этого мы создадим функцию showError, у которой есть два аргумента:
1. property — имя поля, в которое ввели некорректные данные;
2. error — строка с текстом ошибки.
Кроме этого, нужно установить флаг ошибки, присвоив переменной iserror значение true.

Если валидация ошибок не обнаружила, то вызываем функцию sendFormData, которая отправляет данные на сервер. Аргументом этой функции является объект formData. Напоминаем, что в нём хранятся данные формы, записанные в формате 'property': 'value'.

Посмотрим, как теперь будет выглядеть окончательный вариант нашей функции validForm, с учётом выше изложенного:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

validForm(e) {

// отменяем действие браузера по умолчания при клике по

// кнопке формы <button type=»submit»>, чтобы не происходило обновление страницы

e.preventDefault();

// объект представляющий данные HTML формы

const formData = new FormData(this.form);

// объявим переменную error, в которую будем записывать текст ошибки

let error;

// перебираем свойства объекта с данными формы

for (let property of formData.keys()) {

// вызываем функцию, которая будет сравнивать

// значение свойства с паттерном RegExp и возвращать результат

// сравнения в виде пустой строки или текста ошибки валидации

error = this.getError(formData, property);

if (error.length == 0) continue;

// устанавливаем флаг наличия ошибки валидации

this.iserror = true;

// выводим сообщение об ошибке

this.showError(property, error);

}

if (this.iserror) return;

// вызываем функцию отправляющую данные формы,

// хранящиеся в объекте formData, на сервер

this.sendFormData(formData);

}

Теперь настало время разобраться, как вывести ошибки валидации на экран.
Как уже говорилось, для этой цели будем использовать функцию showError с аргументами property и error.

Вывод ошибок валидации на экран.

При создании разметки HTML мы отметили, что ошибку будем выводить в элемент <span> с классом .error. У вас может возникнуть естественный вопрос: в вёрстке четыре таких элемента, как мы определим, в какой из них записывать ошибку? Ведь у них нет ни id, ни атрибутов data-..., с помощью которых можно было бы идентифицировать нужный нам <span>.
Всё очень просто, мы это сделаем с помощью DOM-навигации, но для начала давайте вспомним HTML-разметку для одного поля формы.

<li>

<label>Ваше имя:</label>

<div>

<input class=«form-control» type=«text» name=«username» maxlength=«30» value=«»>

</div>

<span class=«error»></span>

</li>

Рассмотрим ещё раз шаг за шагом DOM-навигацию по элементам, так, как она реализована в функции showError:

  • 1

    Используя аргумент property, который является значением атрибута name элемента <input>, находим этот <input>. Это будет отправная точка DOM-навигации.

  • 2

    Мы видим, что <span class="error">, расположен сразу же после элемента <div>, который является родительским по отношению к <input>. Находим этот <div> с помощью метода DOM-навигации parentElement.

  • 3

    Теперь несложно добраться и до самого <span>, используя метод nextElementSibling и записать в него текст ошибки.

  • 4

    Изменяем стиль отображения <input>, добавив ему новый класс .form-control_error.

Полный код функции showError выглядит так:

showError(property, error) {

// получаем объект элемента, в который введены ошибочные данные

const el = this.form.querySelector(`[name=${property}]`);

// с помощью DOM-навигации находим <span>, в который запишем текст ошибки

const errorBox = Form.getElement(el);

el.classList.add(‘form-control_error’);

// записываем текст ошибки в <span>

errorBox.innerHTML = error;

// делаем текст ошибки видимым

errorBox.style.display = ‘block’;

}

Как видно из приведённого JS-кода, для поиска элемента <span> мы использовали статическую функцию getElement. Код функции очень простой и реализует шаги 2 и 3 приведённого выше алгоритма DOM-навигации. Параметром функции является элемент, в который введены ошибочные данные.
Запишем код функции сразу после конструктора класса Form:

static getElement(el) {

// получение элемента, в который будет выводиться

// текст ошибки

return el.parentElement.nextElementSibling;

}

Проверяем функционирование скрипта валидации формы, внося разные ошибки при заполнении формы. Скрипт прекрасно их отлавливает и выводит сообщения. Кажется, что работу на скриптом валидации мы закончили. Но не будем торопиться.
Вы обратили внимание, что когда пробуете отредактировать текст, относящаяся к нему ошибка не пропадает, создавая определённые неудобства? Давайте решим эту проблему путём удаления содержимого <span class="error">.

Удаление текста ошибки при получении фокуса элементом.

Событие focus вызывается, когда вы нажимаете мышкой на элементе формы. Для того, чтобы отследить появление этого события, давайте опять используем метод addEventListener, применив его к объекту form. Добавим в функцию registerEventsHandler следующий код:

registerEventsHandler() {

// запуск валидации при отправке формы

this.btn.addEventListener(‘click’, this.validForm.bind(this));

// очистка ошибок при фокусе поля ввода

this.form.addEventListener(‘focus’, () => {

// находим активный элемент формы

const el = document.activeElement;

// если этот элемент не <button type=»submit»>,

// вызываем функцию очистки <span class=»error»> от текста ошибки

if (el === this.btn) return;

this.cleanError(el);

}, true);

}

Ну и теперь посмотрим код функции cleanError. Ничего сложно в ней нет, она очень похожа на функцию showError, поэтому отдельно расписывать её не будем.

cleanError(el) {

// с помощью DOM-навигации находим <span>, в который записан текст ошибки

const errorBox = Form.getElement(el);

el.classList.remove(‘form-control_error’);

errorBox.removeAttribute(‘style’);

this.iserror = false;

}

Прекрасно, теперь ошибка пропадает, если мы устанавливаем курсор в поле ввода. Кажется теперь можно перейти к отправке данных формы на сервер.
Давайте пока не будем торопиться.
Будет очень здорово, если мы запустим валидацию введённых данных при потере фокуса элементом, т.е. когда пользователь кликнет на другом месте экрана и курсор уйдёт с текущего поля ввода. Это событие называется blur.

Валидация данных при потере фокуса поля ввода.

Первое, что нам нужно сделать, это повесить обработчики события blur на все поля ввода формы обратной связи. У нас уже есть коллекция этих элементов, которую мы получили при инициализации скрипта — fields. Оптимальным решением будет использование метода for ... of. Этот метод совершает обход коллекции элементов полей ввода и назначает обработчик события blur вызовом метода addEventListener для каждого элемента.
Добавим в конец функции registerEventsHandler следующий код:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

registerEventsHandler() {

// запуск валидации при отправке формы

this.btn.addEventListener(‘click’, this.validForm.bind(this));

// очистка ошибок при фокусе поля ввода

this.form.addEventListener(‘focus’, () => {

// находим активный элемент формы

const el = document.activeElement;

// если этот элемент не <button type=»submit»>,

// вызываем функцию очистки <span class=»error»> от текста ошибки

if (el === this.btn) return;

this.cleanError(el);

}, true);

// запуск валидации поля ввода при потере им фокуса

for (let field of this.fields) {

field.addEventListener(‘blur’, this.validBlurField.bind(this));

}

}

Как видно из кода, при потере фокуса активным элементом, вызывается функция validBlurField. Код функции простой, но есть одна особенность. Перед вызовом функции getError создаётся пустой объект formData. В этот объект заносятся значения property и value, являющиеся свойствами объекта e.target.

Код функции validBlurField и комментарии к нему:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

validBlurField(e) {

const target = e.target;

// имя поля ввода потерявшего фокус

const property = target.getAttribute(‘name’);

// значение поля ввода

const value = target.value;

// создаём пустой объект и записываем в него

// данные в формате ‘имя_поля’: ‘значение’, полученные

// от поля ввода потерявшего фокус

const formData = new FormData();

formData.append(property, value);

// запускаем валидацию поля ввода потерявшего фокус

const error = this.getError(formData, property);

if (error.length == 0) return;

// выводим текст ошибки

this.showError(property, error);

}

Валидация необязательных к заполнению полей формы обратной связи.

Теперь разберём поведение созданного нами скрипта при наличии в форме поля необязательного к заполнению. Давайте создадим такое поле, пусть это будет ввод города.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

<form name=«feedback» method=«POST»>

<ul class=«form-list»>

<li>

<label>Ваше имя:</label>

<div>

<input class=«form-control» type=«text» name=«username» maxlength=«30» value=«»>

</div>

<span class=«error»></span>

</li>

<li>

<label>Электронная почта:</label>

<div>

<input class=«form-control» type=«text» name=«usermail» maxlength=«50» value=«»>

</div>

<span class=«error»></span>

</li>

<li>

<label>Город:</label>

<div>

<input class=«form-control» type=«text» name=«usercity» maxlength=«18» value=«»>

</div>

</li>

<li>

<label>Тема сообщения:</label>

<div>

<input class=«form-control» type=«text» name=«subject» maxlength=«50» value=«»>

</div>

<span class=«error»></span>

</li>

<li>

<label>Текст сообщения:</label>

<div>

<textarea class=«form-control» name=«textmess»></textarea>

</div>

<span class=«error»></span>

</li>

<li>

<label></label>

<div>

<button id=«send_mess» type=«submit»>отправить сообщение</button>

</div>

</li>

</ul>

</form>

Реально таких полей может быть несколько и наша задача исключить ошибки при валидации данных из этих полей ввода. Для этой цели будем проверять наличие свойства, имеющего имя проверяемого поля, в объекте validate. Если такое свойство существует, то данное поле ввода обязательно к проверке.

if (property in validate) {

validate[property]();

}

Обновлённый код функции getError:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

getError(formData, property) {

let error = »;

// создаём литеральный объект validate

// каждому свойству литерального объекта соответствует анонимная функция, в которой

// длина значения поля, у которого атрибут ‘name’ равен ‘property’, сравнивается с 0,

// а само значение — с соответствующим паттерном

// если сравнение истинно, то переменной error присваивается текст ошибки

const validate = {

username: () => {

if (formData.get(‘username’).length == 0 || Form.patternName.test(formData.get(‘username’)) == false) {

error = Form.errorMess[1];

}

},

usermail: () => {

if (formData.get(‘usermail’).length == 0) {

error = Form.errorMess[2];

} else if (Form.patternMail.test(formData.get(‘usermail’)) == false) {

error = Form.errorMess[3];

}

},

subject: () => {

if (formData.get(‘subject’).length == 0) {

error = Form.errorMess[4];

}

},

textmess: () => {

if (formData.get(‘textmess’).length == 0) {

error = Form.errorMess[5];

}

}

}

if (property in validate) {

// если после вызова анонимной функции validate[property]() переменной error

// было присвоено какое-то значение, то это значение и возвращаем,

// в противном случае значение error не изменится

validate[property]();

}

return error;

}

Вот теперь все возможные варианты валидации формы обратной связи рассмотрены, пользователь всё же ввёл корректные данные и теперь они должны быть отправлены на сервер для дальнейшей обработки и отправки в виде письма на наш email.

Асинхронная отправка данных формы обратной связи на сервер.

Для отправки данных на сервер будем использовать функцию sendFormData. Непосредственно отправку данных будем осуществлять с помощью объекта XMLHttpRequest. Он позволяет делать HTTP-запросы к серверу без перезагрузки страницы, т. е. асинхронно. Аргументом функции является объект formData, в котором содержаться имена полей формы и соответствующие им значения.

Код функции sendFormData и комментарии к нему:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

sendFormData(formData) {

let xhr = new XMLHttpRequest();

// указываем метод передачи данных, адрес php-скрипта, который эти данные

// будет обрабатывать и способ отправки данных.

// значение ‘true’ соответствует асинхронному запросу

xhr.open(‘POST’, ‘/sendmail.php’, true);

// xhr.onreadystatechange содержит обработчик события,

// вызываемый когда происходит событие readystatechange

xhr.onreadystatechange = () => {

if (xhr.readyState === 4) {

if (xhr.status === 200) {

// здесь расположен код вашей callback-функции

// например, она может выводить сообщение об успешной отправке письма

} else {

// здесь расположен код вашей callback-функции

// например, она может выводить сообщение об ошибке

}

} else {

// здесь расположен код вашей callback-функции

// например, она может выводить сообщение об ошибке

}

}

// отправляем данные формы

xhr.send(formData);

}

Итак, мы создали форму обратной связи, ввели в неё данные, сделали валидацию этих данных и отправку их на сервер.

Расширяем форму обратной связи.

Данный скрипт не ограничивает форму обратной связи четырьмя полями, их может быть значительно больше. Структура HTML-верстки и JS-скрипта позволяют гибко, без особых усилий расширить данную форму. Например, вы решили добавить поле, в которое пользователь будет вводить номер своего мобильного телефона. Для этого нужно сделать всего три шага:

  • 1

    Добавить поле ввода, для этого можно скопипастить поле ввода имени пользователя. Прописать ему название — «Номер Вашего телефона» и атрибуту name прописать значение ‘userphone’.

  • 2

    В массив с ошибками errorMess добавить текст ошибки, которая может возникнуть при валидации номера телефона.

  • 3

    Создать шаблон RegExp для валидации введённого номера. Добавить объектному литералу validate в функции getError,
    свойство ‘userphone’ и, относящуюся к этому свойству, анонимную функцию, в которой будете использовать созданный шаблон.

По такому же принципу вы можете добавлять и другие поля в свою форму обратной связи. Единственная трудность, которая может вам встретиться — это создание паттерна RegExp, остальное делается простым копипастом соседних полей формы.

Комментарии

Всего: 11 комментариев

Требования при посте комментариев:

  1. Комментарии должны содержать вопросы и дополнения по статье, ответы на вопросы других пользователей.
    Комментарии содержащие обсуждение политики, будут безжалостно удаляться.
  2. Для удобства чтения Вашего кода, не забываейте его форматировать. Вы его можете подсветить код с помощью тега <pre>:

    <pre class="lang:xhtml"> — HTML;
    <pre class="lang:css"> — CSS;
    <pre class="lang:javascript"> — JavaScript.
  3. Если что-то не понятно в статье, постарайтесь указать более конкретно, что именно не понятно.

Понравилась статья? Поделить с друзьями:
  • Ошибка в системе контроля трансмиссии
  • Ошибка в системе завершения работы компьютера
  • Ошибка в системе аварийного вызова фольксваген поло
  • Ошибка в системе аварийного вызова обратитесь в сервисную службу
  • Ошибка в системе microsoft windows kernel power