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

Fetch API — это современный подход для создания асинхронных запросов. Рассмотрим, как отправить данные формы на сервер, используя Fetch API. Научимся отправлять файл вложением (прикреплять к письму файл) вместе с другими полями формы.

  1. Что такое fetch().
  2. Отправка формы с помощью Fetch API.
    1. Fetch API FormData.
    2. Fetch API JSON (application/json).
    3. Отправка файлов (изображений) с помощью Fetch API (multipart/form-data).

1. Fetch API

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

jQuery AJAX:

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

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

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

ES6+

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

Async/Await

(function () {
    getResource("/handler.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 type="submit">Отправить</button>
</form>

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

2.1 Fetch API FormData

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

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

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

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

    if (document.querySelector("form")) {
        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

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

$c = true;

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 "Ошибка. Данные не отправлены.";
};

Используя данный php-обработчик, необходимо в HTML файле в теге form добавить 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))
    };

    if (document.getElementsByTagName("form")) {
        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);
                    })
                    .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();
    }
}

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