Отправка форм при помощи JavaScript
HTML формы могут декларативно отправлять HTTP-запросы. Но формы также могут подготовить HTTP-запросы для отправки с помощью JavaScript, например при помощи XMLHttpRequest . В этой статье исследуются подобные подходы.
Формы не всегда формы
В современных веб-приложениях, одностраничных приложениях и приложениях на основе фреймворков, обычно HTML-формы используются для отправки данных без загрузки нового документа при получении данных ответа. В начале поговорим о том почему это требует другого подхода.
Получение контроля над глобальным интерфейсом
Отправка стандартной HTML формы, как описывалось в предыдущей статье, загружает URL-адрес, по которому были отправлены данные, это означает, что окно браузера перемещается с полной загрузкой страницы. Если избегать полную перезагрузку страницы, можно обеспечить более плавную работу, за счёт предотвращения задержек в сети и возможных визуальных проблем (например, мерцания).
Многие современные пользовательские интерфейсы используют HTML формы только для сбора пользовательского ввода, а не для для отправки данных. Когда пользователь пытается отправить свои данные, приложение берёт контроль и асинхронно передаёт данные в фоновом режиме, обновляя только ту часть всего интерфейса пользователя, которой требуется обновление.
Асинхронная отправка произвольных данных обычно называется AJAX, что означает «Asynchronous JavaScript And XML» (Асинхронный JavaScript и XML).
Чем он отличается?
Объект XMLHttpRequest (XHR) DOM может создавать HTTP-запросы, отправлять их, и получать их результат. Исторически, XMLHttpRequest был разработан для получения и отправки XML в качестве формата обмена, который со временем был заменён на JSON. Но ни XML, ни JSON не вписываются в кодировку запроса данных формы. Данные формы ( application/x-www-form-urlencoded ) состоят из списка пар ключ/значение в кодировке URL. Для передачи бинарных данных, HTTP-запрос преобразуется в multipart/form-data .
Замечание: Сейчас Fetch API часто используется вместо XHR — это современная, обновлённая версия XHR, которая работает в похожем стиле, но имеет несколько преимуществ. Большая часть XHR-кода, которую вы увидите в этой статье можно заменить на Fetch.
Если вы управляете фронтендом (кодом, который выполняется в браузере) и бэкендом (кодом, который выполняется на стороне сервера), вы можете отправлять JSON/XML и обрабатывать их как хотите.
Но если вы хотите использовать сторонний сервис, то вам необходимо отправлять данные в формате, который требуется сервису.
Так как нам следует отправлять подобные данные? Ниже описаны различные необходимые вам техники.
Отправка данных формы
Есть три способа отправки данных формы:
- Создание XMLHttpRequest вручную.
- Использование самостоятельного FormData объекта.
- Использование FormData связанного с <form> элементом.
Давайте рассмотрим их подробнее:
Создание XMLHttpRequest вручную
XMLHttpRequest это самый безопасный и надёжный способ создавать HTTP-запросы. Для отправки данных формы с помощью XMLHttpRequest , подготовьте данные с помощью URL-кодирования, и соблюдайте специфику запросов данных формы.
Посмотрите на пример:
И на JavaScript:
Note: This use of XMLHttpRequest is subject to the same-origin policy (en-US) if you want to send data to a third party web site. For cross-origin requests, you’ll need CORS and HTTP access control.
Using XMLHttpRequest and the FormData object
Building an HTTP request by hand can be overwhelming. Fortunately, the XMLHttpRequest specification provides a newer, simpler way to handle form data requests with the FormData (en-US) object.
The FormData (en-US) object can be used to build form data for transmission, or to get the data within a form element to manage how it’s sent. Note that FormData (en-US) objects are «write only», which means you can change them, but not retrieve their contents.
Using this object is detailed in Using FormData Objects, but here are two examples:
Using a standalone FormData object
You should be familiar with that HTML sample. Now for the JavaScript:
Here’s the live result:
Using FormData bound to a form element
You can also bind a FormData object to an <form> element. This creates a FormData object that represents the data contained in the form.
The HTML is typical:
But JavaScript takes over the form:
Here’s the live result:
You can even get more involved with the process by using the form’s elements property to get a list of all of the data elements in the form and manually manage them one at a time. To learn more about that, see the example in Accessing the element list’s contents в HTMLFormElement.elements.
Dealing with binary data
If you use a FormData (en-US) object with a form that includes <input type=»file»> widgets, the data will be processed automatically. But to send binary data by hand, there’s extra work to do.
There are many sources for binary data, including FileReader , Canvas , and WebRTC. Unfortunately, some legacy browsers can’t access binary data or require complicated workarounds. To learn more about the FileReader API, see Using files from web applications.
The least complicated way of sending binary data is by using FormData (en-US)‘s append() method, demonstrated above. If you have to do it by hand, it’s trickier.
In the following example, we use the FileReader API to access binary data and then build the multi-part form data request by hand:
As you see, the HTML is a standard <form> . There’s nothing magical going on. The «magic» is in the JavaScript:
Here’s the live result:
Conclusion
Depending on the browser and the type of data you are dealing with, sending form data through JavaScript can be easy or difficult. The FormData (en-US) object is generally the answer, and you can use a polyfill for it on legacy browsers.
Отправка писем с помощью JavaScript
JavaScript – это язык программирования, который можно использовать как для интерфейсной, так и для внутренней разработки. Когда JavaScript упоминается в контексте отправки электронных писем, Node.js – это первое, что приходит на ум. И сегодня мы разберем, как использовать JS для отправки электронных писем из приложения, у которого нет сервера.
FAQ: Могу ли я отправлять электронные письма с JS или нет?
Вы не можете отправлять электронные письма, используя только код JavaScript, из-за отсутствия поддержки серверных соединений. Для этого вам понадобится серверный язык, который общается с SMTP-сервером. Вы можете использовать JS вместе с серверным скриптом, который будет отправлять электронные письма из браузера на основе ваших запросов.
Почему вы можете захотеть отправлять электронные письма с помощью JS
Традиционно серверная часть обычного приложения отвечает за отправку электронных писем. Вам нужно будет настроить сервер с использованием внутренней технологии. Клиентская сторона отправляет запрос на сервер, который создает электронное письмо и отправляет его на SMTP-сервер.
Итак, почему кто-то может пойти другим путем и отправлять электронные письма прямо со стороны клиента с помощью JavaScript? Такой подход весьма полезен для создания контактных форм или других видов взаимодействия с пользователем в веб-приложениях, что позволяет вашему приложению отправлять электронную почту без обновления страницы, с которой взаимодействует пользователь. Кроме того, вам не нужно возиться с кодированием сервера. Это веский аргумент, если ваше веб-приложение использует отправку электронной почты только для контактных форм. Ниже вы найдете несколько вариантов того, как заставить ваше приложение отправлять электронные письма со стороны клиента.
mailto: для отправки формы данных
Поскольку вы не можете отправить электронное письмо напрямую с помощью JS, вы можете указать браузеру открыть для этого почтовый клиент по умолчанию. Технически метод mailto: не отправляет электронную почту прямо из браузера, но может выполнять следующую работу:
Когда вы запустите код в браузере, вы увидите следующее:
После отправки данных браузер открывает почтовый клиент по умолчанию. В нашем случае это Gmail.
Метод mailto: является довольно простым решением для реализации , но он имеет некоторые специфические недостатки:
- Вы не можете контролировать макет данных, поскольку данные отправляются в форме, отправленной браузером.
- mailto: не защищает ваш адрес электронной почты от спам-ботов. Некоторое время назад это можно было смягчить, построив ссылку в JS. В наши дни все больше и больше ботов запускают JS и не полагаются на HTML, отображаемый только сервером.
SmtpJS.com – отправка писем из JavaScript
SmtpJS – это бесплатная библиотека, которую вы можете использовать для отправки писем из JavaScript. Все, что вам нужно, – это SMTP-сервер и несколько манипуляций, чтобы все было сделано. Мы будем использовать Mailtrap.io в качестве сервера, потому что это действенное решение для тестирования электронной почты. Ниже приведен порядок действий, которому вы должны следовать:
- Создайте HTML-файл (например, test.html) со следующим скриптом:
<script src=»https://smtpjs.com/v3/smtp.js»> </script> - Создайте кнопку, которая будет запускать функцию JavaScript.
<input type=»button» value=»Send Email» onclick=»sendEmail()»> - Напишите функцию JS для отправки писем через SmtpJS.com.
- Запустите test.html в браузере и отправьте письмо
Недостатком приведенного выше примера является то, что ваше имя пользователя и пароль видны в клиентском скрипте. Это можно исправить, если использовать параметр шифрования, предоставляемый SmtpJS. Нажмите кнопку «Зашифровать свои учетные данные SMTP» и заполните необходимые поля.
После этого нажмите “Сгенерировать токен безопасности” и затем используйте его в своей функции JS вместо настроек SMTP-сервера, как показано ниже:
Работа с формами
Помимо стандартных средств работы с формами можно использовать JavaScript, чтобы проверять формы на валидность, получать доступ к значениям и отправлять информацию на сервер.
Трюки для работы с формами в JS проще всего показать на примере. В этой статье мы соберём форму заявки на участие в миссии по колонизации Марса. В этой форме мы немножко приправим стандартные HTML-атрибуты динамикой на JS.
Разметка и требования
Наша форма заявки на участие в миссии «Mars Once» будет состоять из шести полей. В форме мы собираем следующие данные:
- Имя, чтобы знать, как обращаться в ответном письме.
- Почту, чтобы знать, куда это письмо слать.
- Возраст — нужны только молодые ♂️
- Специализацию — инженеры и учёные пригодятся для основной работы, а психологи нужны, чтобы команда друг друга не перегрызла за десятилетнюю колонизаторскую миссию.
- Работал ли человек в NASA — это большой плюс.
- Фотография, чтобы использовать в печатных материалах.
В целом форма рабочая: обязательные поля не пропустят пустые значения, атрибут type проследит, чтобы вместо почты нам не прислали номер телефона, а по нажатию на кнопку валидная форма отправит все данные.
Но нам кроме всего этого хочется:
- чтобы страница при отправке не перезагружалась;
- чтобы во время запроса показывался лоадер, при успешной отправке — поздравление, а при ошибке — причина ошибки;
- чтобы кнопка была заблокирована до тех пор, пока форма не валидна.
Отправка без перезагрузки
Первым делом настроим отправку формы без перезагрузки страницы.
Перезагрузка страницы — это поведение по умолчанию для отправки формы. Чтобы его предотвратить, нам нужно «перехватить» управление в момент отправки и сказать форме, что делать вместо этого.
Предотвращаем отправку данных
Для «предотвращения» срабатывания событий мы можем использовать метод preventDefault ( ) на объекте события. В нашем случае событием будет отправка формы — submit .
Если наше событие находится в переменной event , то для предотвращения поведения по умолчанию мы можем вызвать event . preventDefault ( ) .
Чтобы «соединить» форму с нашей будущей собственной отправкой данных, мы напишем функцию, которая будет «слушать» событие отправки и реагировать на него.
Найдём форму на странице, с помощью getElementById и подпишемся на событие submit с помощью addEventListener . Пока мы не будем отправлять форму, а просто напечатаем в консоль строку «Отправка!» и убедимся, что механизм работает:
Мы можем просто передать функцию handleFormSubmit как второй аргумент в addEventListener , так как он автоматически передаст событие в качестве аргумента для handleFormSubmit .
Получится, что при отправке формы сработает событие submit , которое запустит наш обработчик handleFormSubmit .
В этот обработчик как аргумент event будет передано событие отправки. Мы вызовем event . preventDefault ( ) , и форма не отправится самостоятельно.
Собираем данные из формы
Следующий шаг — собрать всё, что необходимо отправить.
Нам не хочется собирать каждое значение отдельно.
- Это может быть долго: если форма состоит из 10 полей, это уже требует достаточно много кода.
- Это не масштабируется: если мы захотим добавить ещё пару полей, нам придётся писать код и для этих полей тоже.
Вместо этого мы будем использовать возможности языка, чтобы достать все поля и элементы управления из формы. Напишем функцию serializeForm :
Аргумент функции serializeForm — это элемент формы. Именно элемент — не селектор, а конкретный узел в DOM-дереве.
У форм есть свойство elements , которое содержит в себе все элементы управления и поля этой формы. Именно этим свойством мы воспользуемся, чтобы получить все данные из формы.
Если сейчас мы вызовем эту функцию, передав туда нашу форму как аргумент, то в консоли появится список всех элементов:
Обратите внимание, что тип этого набора элементов — HTMLFormControlsCollection . Это не массив и, чтобы пройтись циклом по списку элементов, нужно превратить его в массив с помощью вызова Array . from ( ) .
Нам останется собрать имя и значение каждого из полей. Для начала, выведем имя и значение каждого элемента в консоль:
Мы получили список элементов, преобразовали его в массив и прошлись по каждому элементу. У каждого элемента получили поля name и value и вывели их в консоль.
В консоли после запуска получим вывод по каждому из полей:
Заметим, что последняя строчка не имеет ни названия, ни значения. Это потому, что последний элемент, который мы проверяли — это кнопка.
Чтобы элементы без названия нам не мешались, мы отфильтруем наш набор. Воспользуемся методом filter , чтобы отбросить элементы с пустым именем. Также заменим метод forEach на map — он соберёт нам массив, который хранит объект с именем и значением каждого отфильтрованного элемента.
На выходе в консоли получится массив из объектов с name и value :
Значения чекбоксов
Сейчас можно заметить, что nasa — experience имеет значение «1» . Это неправильно:
- мы не отмечали чекбокс, а значение почему-то «1» ;
- в целом хотелось бы, чтобы значение этого поля было булевым.
Для этого мы можем использовать особое свойство checked , которое есть у чекбоксов.
Значение этого поля как раз булево, и мы можем использовать это в нашей функции serializeForm .
Но это свойство мы хотим использовать только на чекбоксе, а не на остальных полях. Это тоже можно сделать. Прочитаем тип элемента и, если он «checkbox» , то возьмём в качестве значения поле checked :
Теперь значение поля nasa — experience будет true , если чекбокс отмечен, и false , если пропущен. Увидим такой вывод:
Формат данных
В целом, нынешний формат данных в виде массива объектов нам может и подойти, но мы с вами используем кое-что лучше — FormData .
FormData — это особый тип данных, который можно использовать для отправки данных формы на сервер.
Мы воспользуемся им, чтобы сохранить данные из формы. Создадим экземпляр с помощью new FormData ( ) , откажемся от массива со значениями и будем добавлять имена полей и их значения в FormData с помощью вызова функции append :
Но так как тип FormData специально создан для работы с формами, можно сделать гораздо проще
Стоит отметить, что nasa — experience в таком случае попадёт в финальные данные, только если чекбокс отметили. Если его не отметить, то в финальных данных он не окажется.
Когда чекбокс nasa — experience выделен, получим такой вывод:
Когда чекбокс не выделен — такой:
В первом случае чекбокс был отмечен, поэтому в списке есть элемент nasa — experience , во втором случае чекбокс был пропущен, поэтому такого элемента в списке данных нет.
Чтобы проверить, какие данные в себе содержит переменная типа FormData , можно использовать метод . entries ( ) , он выведет список с данными, как в примере выше.
Отправка на сервер
Теперь нам надо данные из формы отправить на сервер. Представим, что наш бэкенд предоставляет API-эндпоинт для сохранения данных. Попробуем отправить их.
Функция будет асинхронной, потому что работает с сетевыми запросами. В качестве аргумента она принимает FormData и отправляет запрос с помощью вызова fetch . Нам нужно указать правильный заголовок Content — Type у запроса, для формы он ‘multipart / form — data’ :
Функция вернёт результат запроса к серверу, который мы сможем проверить на ошибки.
Теперь используем эту функцию в обработчике события отправки. Сериализуем форму и передадим её в функцию отправки. Вместо обращения напрямую к форме, будем читать её из объекта события. Форма в объекте события submit будет храниться в свойстве target :
Обратите внимание, что функция handleFromSubmit стала асинхронной, так как она вызывает другую асинхронную функцию и дожидается её результата. Внутри response будет поле status , по которому мы сможем определить, успешно ли прошёл запрос и вывести соответствующее сообщение пользователю.
Обработка загрузки и вывод сообщения о результате
Теперь немножко улучшим UX нашей формы. Сейчас она просто отправляет данные и ничего не сообщает пользователям. Это не круто, потому что отправителю будет непонятно, получилось ли записаться в «Mars Once» или нет.
- при отправке формы показывать лоадер, пока идёт запрос;
- при успешной отправке показать сообщение, что форма отправлена, и спрятать форму;
- при ошибке указать пользователю, где именно была ошибка.
Начнём с лоадера.
Показываем лоадер во время отправки
У нас вместо лоадера будет просто показываться строка «Sending. »
Добавим его после кнопки и спрячем:
Прячем мы его, потому что хотим показать только во время запроса. Для этого напишем функцию, которые будут управлять его состоянием — делать лоадер видимым, если он не виден сейчас, и скрывать, если он виден. Так как технически это добавление и удаление класса hidden , то можно воспользоваться функцией toggle из classList API:
Вызовем эту функцию до отправки запроса, чтобы показать лоадер, и после запроса, чтобы скрыть. Лоадер будет виден до тех пор, пока запрос не завершится:
Обрабатываем успешную отправку
Давайте теперь проверять ответ сервера. Допустим, нам хочется, чтобы при успешной отправке мы показывали alert с сообщением об успешной отправке и прятали форму:
Мы должны вызвать onSuccess , только если форма была отправлена успешна. Для этого добавим проверку на статус ответа сервера — он должен быть 200 в случае успеха (статусы ответа разобраны в статье про HTTP протокол):

При успешной отправке покажется это сообщение, а форма пропадёт.
Обрабатываем ошибки
Если что-то пошло не так, то мы хотим сказать пользователям об этом. Напишем функцию, которая будет вызывать alert с сообщением, которое пришлёт сервер в случае ошибки:
Мы могли бы вызвать alert сразу на месте, но лучше вынести обработку ошибки в отдельную функцию. Так, если нам захочется добавить какие-то действия по обработке ошибок, нам будет проще ориентироваться в коде.
Вместе со статусом будем получать информацию об ошибке из поля error . Если запрос был успешным, то error будет пустым, но в случае ошибки там будет лежать сообщение:

Если что-то пошло не так, мы увидим причину. Форма останется на месте.
Блокируем кнопку отправки на невалидной форме
Сейчас кнопку отправки можно нажать в любой момент, даже если форма невалидна. И хоть пользователь не сможет отправить форму из-за HTML-валидации, было бы неплохо предупредить, что кнопку нажимать пока рано.
Давайте будем её блокировать до тех пор, пока не будут заполнены все поля, которые требуется заполнить.
Напишем функцию, которая будет проверять валидность формы и блокировать кнопку, если требуется. Аргументом она будет принимать событие ввода с клавиатуры на полях ввода.
Так как событие ввода будет происходить на полях, а не на самой форме, то значение event . target — это поле. Чтобы получить форму, воспользуемся свойством form , значением которого является ссылка на родительскую форму.
Проверять валидность формы будем с помощью метода checkValidity ( ) формы. Он запускает стандартные проверки. Результат проверки будем использовать для того чтобы установить свойство disabled кнопки в значение true , если нужно заблокировать, и false , если кнопка должна быть доступна.

Теперь, пока форма не будет заполнена, кнопка будет заблокирована.
Что у нас получилось
Мы сделали форму, которая отправляет данные без перезагрузки страницы, показывает сообщение об ошибке или успешной отправке и блокирует кнопку, пока не введены значения.

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