Отправка Данных Формы Используя Fetch API

Fetch API - это современный подход для создания асинхронных запросов. Рассмотрим, как отправить данные формы на сервер, используя Fetch API. Научимся отправлять файл вложением (прикреплять к письму файл) вместе с другими полями формы.
  • 1. Что такое fetch().
  • 2. Отправка формы с помощью Fetch API.
  • 2.1 Fetch API FormData.
  • 2.2 Fetch API JSON (application/json).
  • 2.3 Отправка файлов (изображений) с помощью Fetch API (multipart/form-data).

1. Fetch API

Метод fetch() позволяет создавать асинхронные запросы наподобие ajax(). Сравним синтаксис jQuery AJAX и Fetch API на примере отправки данных на сервер.

jQuery AJAX

$.ajax({
    url: 'handlerName.php',
    type: 'POST',
    data: data
}).done(function(data){
    ...
}).fail(function(){
    ...
});

Синтаксис fetch().

fetch('handlerName.php', {
    method: 'POST',
    body: data
}).then(function(response){
    ...
}).catch(function(error){
    ...
});

ES6+

fetch('handlerName.php', {
    method: 'POST',
    body: data
})
  .then(response => ...)
  .catch(error => console.error(error));

Async/Await

(function () {
    getResource('/handlerName.php')
        .then(data => response(data))
        .catch(error => console.error(error));
}());

async function getResource(url) {
    const res = await fetch(url, { // ждём ответ, только тогда наш код пойдёт дальше

        method: 'POST',
        body: data
    });

    if (!res.ok) { // код ответа не 200~

        throw new Error(`Не удалось получить ${url}, статус: ${res.status}`);
    }
    return await res.json();
}

function response(resp) {
    ...
}

2. Отправка формы с помощью Fetch API

Рассмотрим пример, как можно реализовать отправку данных формы на e-mail.

<form>
    <input type="text" name="Имя">
    <input type="tel" name="Телефон">
    <input type="email" name="E-mail">
    <textarea name="Сообщение"></textarea>
    <button>Отправить</button>
</form>

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

2.1 Fetch API FormData

Отправим данные, используя объект FormData.

В данном случае форма будет отправлена с заголовком Content-Type: form/multipart.

document.addEventListener('DOMContentLoaded', () => {

    const ajaxSend = async (formData) => {
        const fetchResp = await fetch('mail.php', {
            method: 'POST',
            body: formData
        });
        if (!fetchResp.ok) {
            throw new Error(`Ошибка по адресу ${url}, статус ошибки ${fetchResp.status}`);
        }
        return await fetchResp.text();
    };

    const forms = document.querySelectorAll('form');
    forms.forEach(form => {
        form.addEventListener('submit', function (e) {
            e.preventDefault();
            const formData = new FormData(this);

            ajaxSend(formData)
                .then((response) => {
                    console.log(response);
                    form.reset(); // очищаем поля формы

                })
                .catch((err) => console.error(err))
        });
    });

});

Обработать данные на сервере вы можете как хотите. Пример mail.php

<?php
$method = $_SERVER['REQUEST_METHOD'];
$c = true;

$project_name = trim($_POST["project_name"]);
$admin_email  = trim($_POST["admin_email"]);
$form_subject = trim($_POST["form_subject"]);

foreach ( $_POST as $key => $value ) {
  if ( is_array($value) ) {
    $value = implode(", ", $value);
  }
  if ( $value != "" && $key != "project_name" && $key != "admin_email" && $key != "form_subject" ) {
    $message .= "
    " . ( ($c = !$c) ? '<tr>':'<tr style="background-color: #f8f8f8;">' ) . "
      <td style='padding: 10px; border: #e2dddd 1px solid;'><b>$key</b></td>
      <td style='padding: 10px; border: #e2dddd 1px solid;'>$value</td>
    </tr>
    ";
  }
}

$message = "<table style='width: 100%;'>$message</table>";

function adopt($text) {
    return '=?UTF-8?B?'.Base64_encode($text).'?=';
}

$headers = "MIME-Version: 1.0" . PHP_EOL .
"Content-Type: text/html; charset=utf-8" . PHP_EOL .
'From: '.adopt($project_name).' <'.$admin_email.'>' . PHP_EOL .
'Reply-To: '.$admin_email.'' . PHP_EOL;

if (mail($admin_email, adopt($form_subject), $message, $headers )) {
    http_response_code(200);
    echo "Данные отправлены.";
} else {
    http_response_code(400);
    echo "Ошибка. Данные не отправлены.";
};

Используя данный обработчик, необходимо в HTML файле в теге form добавить 3 скрытых input'а :
1. Имя сайта.
2. Почту, на которую будут приходить данные.
3. Метка, с описанием места отправки формы.

<input type="hidden" name="project_name" value="SiteName">
<input type="hidden" name="admin_email"  value="yourMail">
<input type="hidden" name="form_subject" value="Форма с подвала сайта SiteName">

2.2 Fetch API JSON

Чтобы отправить данные в формате JSON воспользуйтесь следующим кодом.

document.addEventListener('DOMContentLoaded', () => {

    const ajaxSend = (formData) => {
        fetch('mail.php', { // файл-обработчик

            method: 'POST',
            headers: {
                'Content-Type': 'application/json', // отправляемые данные

            },
            body: JSON.stringify(formData)
        })
            .then(response => alert('Сообщение отправлено'))
            .catch(error => console.error(error))
    };

    const forms = document.getElementsByTagName('form');
    for (let i = 0; i < forms.length; i++) {
        forms[i].addEventListener('submit', function (e) {
            e.preventDefault();

            let formData = new FormData(this);
            formData = Object.fromEntries(formData);

            ajaxSend(formData)
                .then((response) => {
                    console.log(response);
                    form.reset(); // очищаем поля формы

                })
                .catch((err) => console.error(err))
        });
    };
});

Тогда в mail.php необходимо добавить строчку для декодирования JSON.

$_POST = json_decode(file_get_contents("php://input"), true);

Таким образом можно отправить данные формы на почту, используя Fetch API.

2.3 Отправка файлов (изображений) с помощью Fetch API

Рассмотрим, как отправлять изображения с помощью Fetch API.

В данном примере мы будем отправлять текстовые поля с вложенным (прикреплённым) файлом, в данном случае картинкой.

<form>
    <div>
        <label for="some_text">Введите текст</label>
        <input type="text" name="Текстовое поле" id="some_text">
    </div>
    <div>
        <label for="email">Введите email</label>
        <input type="text" name="E-mail" id="email">
    </div>
    <div>
        <label for="file_attach">Выберите изображение для загрузки.</label>
        <input type="file" name="file_attach" id="file_attach">
    </div>
    <button type="submit">Отправить</button>

    <input type="hidden" name="admin_email" value="Почта админа">
    <input type="hidden" name="form_subject" value="Отправка файла Fetch API">
</form>

JavaScript.

document.addEventListener('DOMContentLoaded', () => {

    const file_attach = document.getElementById('file_attach'); // файл

    const forms = document.querySelectorAll('form');            // формы


    const message = {
        loading: 'Отправка...',
        success: 'Спасибо. Форма отправлена',
        failed: 'Что-то пошло не так...'
    };

    // обработчик события 'change' (происходит после выбора файла)

    file_attach.addEventListener('change', () => {
        uploadFile(file_attach.files[0]);
    });

    // Загрузка файла

    const uploadFile = (file) => {
        console.log(file.name);

        // провераяем тип файла

        if (!['image/jpeg', 'image/png', 'image/gif', 'image/svg+xml'].includes(file.type)) {
            alert('Разрешены только изображения.');
            return;
        }

        // проверим размер файла (<2 Мб)

        if (file.size > 2 * 1024 * 1024) {
            alert('Файл должен быть менее 2 МБ.');
            return;
        }

    };

    // отправляем `POST` запрос

    const postData = async (url, fData) => { // имеет асинхронные операции

        document.querySelector('.status').innerHTML = message.loading; // в процессе


        // ждём ответ, только тогда наш код пойдёт дальше

        let fetchResponse = await fetch(url, {
            method: 'POST',
            body: fData
        });

        // ждём окончания операции

        return await fetchResponse.text();
    };

    if (forms) {
        forms.forEach(el => {
            el.addEventListener('submit', function (e) {
                e.preventDefault();

                // Блок со статусом отправки

                let statusMessage = document.createElement('div');
                statusMessage.classList.add('status');
                el.appendChild(statusMessage);

                // создание объекта FormData

                const fData = new FormData(el);
                fData.append('file_attach', file_attach.files[0]); // добавляем файл в объект FormData()


                // Отправка на сервер

                postData('/send_pultipart.php', fData)
                    .then(fetchResponse => {
                        statusMessage.innerHTML = message.success;
                    })
                    .catch(() => statusMessage.innerHTML = message.failed)
                    .finally(() => {
                        this.reset(); // очищаем поля формы

                        setTimeout(() => {
                            statusMessage.remove(); // удаляем статус

                        }, 5000);
                    });

            });
        });
    };
});

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

<?php

$project_name = "Форма с сайта ".$_SERVER['HTTP_REFERER'];
$admin_email  = trim($_POST["admin_email"]);
$form_subject = trim($_POST["form_subject"]);

$file_attach = "";

// Если поле выбора вложения не пустое - закачиваем его на сервер

if (!empty($_FILES['file_attach']['tmp_name'])) {
    $path = __DIR__ . "/upload-files/" . $_FILES['file_attach']['name']; // путь загрузки файла

    if (copy($_FILES['file_attach']['tmp_name'], $path)) $file_attach = $path;
}

$c = true; // Script Foreach

foreach ( $_POST as $key => $value ) {
    if ( is_array($value) ) {
      $value = implode(", ", $value);
    }
    if (
        $value != "" &&
        $key != "project_name" &&
        $key != "admin_email" &&
        $key != "form_subject" &&
        $key != "file_attach"
    ) {
      $message .= "
      " . ( ($c = !$c) ? '<tr>':'<tr style="background-color: #f8f8f8;">' ) . "
        <td style='padding: 10px; border: #e9e9e9 1px solid;'><b>$key</b></td>
        <td style='padding: 10px; border: #e9e9e9 1px solid;'>$value</td>
      </tr>
      ";
    }
}

$message = "<table style='width: 100%;'>
    <tr>
        <td style='padding:10px; border:#e9e9e9 1px solid; text-align:center' colspan='2'>
            <big>$project_name</big>. $form_subject
        </td>
    </tr>
    $message
</table>";

// Отправляем сообщение

if ( empty($file_attach) ) {
    $headers = "MIME-Version: 1.0" . PHP_EOL .
    "Content-Type: text/html; charset=utf-8" . PHP_EOL .
    'From: '.$project_name.' <'.$admin_email.'>' . PHP_EOL .
    'Reply-To: '.$admin_email.'' . PHP_EOL;

    mail($admin_email, $form_subject, $message, $headers);
} else {
    send_mail($admin_email, $form_subject, $message, $file_attach);
}

// Функция для отправки сообщения с вложением

function send_mail($to, $form_subject, $html, $path) {
    $fp = fopen($path, "r");

    if (!$fp) {
        echo "Файл $path не может быть прочитан";
        exit();
    }

    $file = fread($fp, filesize($path));
    fclose($fp);

    $boundary = "--".md5(uniqid(time())); // генерируем разделитель


    $headers .= "MIME-Version: 1.0\n";
    $headers .= "Content-Type: multipart/mixed; boundary=\"$boundary\"\n";

    $multipart .= "--$boundary\n"; 

    $multipart .= "Content-Type: text/html; charset='utf-8'\n";
    $multipart .= "Content-Transfer-Encoding: Quot-Printed\n\n";
    $multipart .= "$html\n\n";

    $message_part = "--$boundary\n";
    $message_part .= "Content-Type: application/octet-stream\n";
    $message_part .= "Content-Transfer-Encoding: base64\n";
    $message_part .= "Content-Disposition: attachment; filename = \"".$path."\"\n\n"; 
    $message_part .= chunk_split(base64_encode($file))."\n"; 

    $multipart .= $message_part."--$boundary--\n"; 

    if (!mail($to, $form_subject, $multipart, $headers)) {
        echo "К сожалению, письмо не отправлено";
        exit();
    }

}

Если вам понравилась статья, то рекомендую к прочтению: Как передать данные из одной формы в другую

admin