Js отправка файла после выбора

Обновлено: 04.07.2024

Помимо стандартных средств работы с формами можно использовать 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() , откажемся от массива со значениями и будем добавлять имена полей и их значения в FromData с помощью вызова функции append :

Но так как тип FormData специально создан для работы с формами, можно сделать гораздо проще 🙂

Стоит отметить, что nasa-experience в таком случае попадёт в финальные данные, только если чекбокс отметили. Если его не отметить, то в финальных данных он не окажется.

Когда чекбокс nasa-experience выделен, получим такой вывод:

Когда чекбокс не выделен — такой:

В первом случае чекбокс был отмечен, поэтому в списке есть элемент nasa-experience , во втором случае чекбокс был пропущен, поэтому такого элемента в списке данных нет.

Чтобы проверить, какие данные в себе содержит переменная типа FormData , можно использовать метод .entries() , он выведет список с данными, как в примере выше.

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

Функция будет асинхронной, потому что работает с сетевыми запросами. В качестве аргумента она принимает FormData и отправляет запрос с помощью вызова fetch . Нам нужно указать правильный заголовок Content-Type у запроса, для формы он 'multipart/form-data' :

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

Теперь используем эту функцию в обработчике события отправки. Сериализуем форму и передадим её в функцию отправки. Вместо обращения напрямую к форме, будем читать её из объекта события. Форма в объекте события submit будет храниться в свойстве target :

Теперь немножко улучшим UX нашей формы. Сейчас она просто отправляет данные и ничего не сообщает пользователям. Это не круто, потому что отправителю будет непонятно, получилось ли записаться в «Mars Once» или нет.

Начнём с лоадера.

Показываем лоадер во время отправки

У нас вместо лоадера будет просто показываться строка «Sending. »

Добавим его после кнопки и спрячем:

Прячем мы его, потому что хотим показать только во время запроса. Для этого напишем функцию, которые будут управлять его состоянием — делать лоадер видимым, если он не виден сейчас, и скрывать, если он виден. Так как технически это добавление и удаление класса hidden , то можно воспользоваться функцией toggle из classList API:

Вызовем эту функцию до отправки запроса, чтобы показать лоадер, и после запроса, чтобы скрыть. Лоадер будет виден до тех пор, пока запрос не завершится:

Обрабатываем успешную отправку

Обрабатываем ошибки

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

Если что-то пошло не так, мы увидим причину. Форма останется на месте.

Блокируем кнопку отправки на невалидной форме

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

Напишем функцию, которая будет проверять валидность формы и блокировать кнопку, если требуется. Аргументом она будет принимать событие ввода с клавиатуры на полях ввода.

Так как событие ввода будет происходить на полях, а не на самой форме, то значение event.target — это поле. Чтобы получить форму, воспользуемся свойством form , значением которого является ссылка на родительскую форму.

Проверять валидность формы будем с помощью метода checkValidity() формы. Он запускает стандартные проверки. Результат проверки будем использовать для того чтобы установить свойство disabled кнопки в значение true , если нужно заблокировать, и false , если кнопка должна быть доступна.

Скриншот заблокированной кнопки отправки

Теперь, пока форма не будет заполнена, кнопка будет заблокирована.

Что у нас получилось

Итоговый вид формы

Для всего этого мы использовали методы HTML-элементов и элементов форм, которые нам предоставляет браузер и веб-платформа.

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

Недавно столкнулся с интересной задачей: на странице нужно разместить пользовательскую форму, в которой было несколько полей для ввода данных и поле для выбора файла. Причем, кнопка выбора файла сделана именно как красивая кнопка, а не стандартный компонент input[file]. А отправка этой формы, в соответствии с техническим заданием, должна выполняться сразу после выбора файла. В итоге должна получится подобная пользовательская форма:

Здесь можно выделить две отдельные задачи. Первая – выполнить отправку формы сразу после выбора файла. Вторая – оформить компонент выбора файла в соответствии с дизайном в виде нестандартной кнопки.

Форма, для которой реализуем все описанные задачи, имеет следующий исходный код:

и в начале выглядит так:

Submit формы после выбора файла в input[file]

Подобная задача рассматривалась в статье о перезагрузке и обновлении страницы выбором в select . Там так же использовалось событие компонента формы для отправки формы с помощью скрипта.

Выполнить отправку формы можно с помощью jQuery кода:

Этот вызов нужно привязать к событию change (обновлению) компонента выбора файла input[type=file]. В итоге код jQuery для решения нашей формы должен быть следующий:

Собственное оформление input[file] средствами CSS

Сложность этой задачи заключается в отсутствии стандартов оформления компонента. В различных браузерах он отображается его по-разному. А CSS стилей, определяющих его внешний вид не достаточно, чтобы оформить его.

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

Одним из вариантов может быть снова применение jQuery. Но, на мой взгляд, если есть возможность обойтись без дополнительных скриптов, а использовать только теги HTML, то стоит делать без скриптов. И здесь нам на помощь приходит тег HTML label. В статье "Переключение radio кнопки и checkbox нажатием на подпись в HTML" уже описывалось его применение.

Здесь, подобным образом, тег компонента выбора файла обрамляется тегом label. В label так же добавим текст "Загрузить файл"

Теперь остается только назначить стили для оформления имеющихся тегов. Компонент выбора файлов скрывается. Тегу label, придаем нужный внешний вид:

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

Легкий способ добавления файловой формы в основную форму, но это запрещено.

ОТВЕТЫ

Ответ 1

Использование jQuery

Я бы создал кнопку и невидимый вход вроде этого

и добавьте несколько jQuery для его запуска:

Использование Vanilla JS

Такая же идея, без jQuery (кредиты @Pascale)

Ответ 2

Только JS - нет необходимости в jquery

Просто создайте элемент ввода и запустите щелчок.

Это самое простое, всплывающее диалоговое окно выбора файла, но его бесполезно без обработки выбранного файла.

Обработка файлов

Добавление события onchange во вновь созданный вход позволит нам делать что-то, как только пользователь onchange файл.

На данный момент у нас есть файловая переменная, хранящая различную информацию:

Но что, если нам нужно содержимое файла?

Для того, чтобы получить фактическое содержание файла, по разным причинам. поместите изображение, загрузите на холст, создайте окно с URL-адресом Base64 и т.д. нам нужно будет использовать API FileReader

Мы должны создать экземпляр FileReader и загрузить ссылку на выбранный пользователем файл.

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

Пример - "Stackoverflow новое фоновое изображение!"

Давайте попробуем создать диалог выбора файла, чтобы изменить фоновое изображение stackoverflows на что-то более пряное.

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

Ответ 3

Изменить: я не проверял это в Blink, он на самом деле не работает с <button> , но он должен работать с большинством других элементов - по крайней мере, в последних браузерах.

Проверьте эту скрипку с кодом выше.

Ответ 4

Для полноты, Рон ван дер Хейден решение в чистом JavaScript:

Ответ 5

Чтобы расширить ответ от 'levi' и показать, как получить ответ от загрузки, чтобы вы могли обработать загрузку файла:

Ответ 6

ГОТОВ ИСПОЛЬЗОВАТЬ ФУНКЦИЮ (используя Promise)

Ответ 7

Сначала объявите переменную для хранения имен файлов (чтобы использовать их позже):

Диалог открытия файла

Я публикую это, так что это может кому-то помочь, потому что в интернете нет четких инструкций о том, как хранить имена файлов в массиве!

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

Суть задачи

Для нетерпеливых сразу ссылки на демо приложения и исходники

Подготовим шаблон проекта.

В корень проекта положим файл index.html, в папку js файлы jquery.min.js и main.js (не забудем их подключить в index.html). Серверный код будет выполнять upload.php из папки php, а для полученных файлов создадим папку upload.


Получится так:

html-заготовка формы

Создадим простейшую форму в index.html

Заметим, что никаких атрибутов, вроде enctype у формы или name у input-ов, навешивать не нужно - все сделает javascript.

Базовый клиентский код

Мы создали модуль приложения app, и в методе init подключили обработку сабмита формы. В функции submitForm данные для отправки готовятся с помощью объекта formdata соответствующего класса FormData. Перебирая все поля выбора файлов .js-photos, мы добавляем файлы к этому объекту методом append. Обратите внимание на название photos[] - квадратные скобки обязательны, так как в противном случае на сервер попадет не массив файлов, а только один.

Валидацию пока не делаем, рассмотрим ее ниже. А пока переходим к серверной части.

Принимаем файлы на сервере и перемещаем в нужную папку

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

Сначала мы извлекаем массив файлов из $_FILES['photos'] и отпределяем папку назначения - куда мы копируем искомые файлы. PHP предварительно копирует файлы во временную папку, свойство tmp_name из $_FILES даст нам полный путь к этому временному файлу. Далее перебираем наш массив и перемещаем файлы в нужную папку под тем же названием, с которым он пришел с клиента. Этим занимается функция move_uploaded_file. В конце возвращаем клиенту успешный код ответа.

Валидация файлов на клиенте

  • 1. Выбран ли вообще файл
  • 2. Не превышает ли его размер максимальный
  • 3. Подходит ли файл по формату

Например, ошибка может выглядеть так.

Это означает, что в input[0] (в первом по счету поле) пользователь добавил файл test.pdf, формат которого не поддерживается, так как мы просим изображения. Это одна ошибка валидации. А массив таких ошибок и будет результатом выполнения функции. Если ошибок нет, все файлы выбраны и заданы правильно, то вернем пустой массив.

Впрочем, сейчас посмотрим, как это работает. Добавим функцию валидации validateFiles.

В параметре options мы передаем объект из трех полей: $files, maxSize и types. Соответственно, это jQuery-массив элементов input, максимальный размер файла в байтах и массив допустимых типов-расширений файлов, например, image/jpg, image/png или application/pdf - список типов легко можно загуглить.

Мы перебираем массив файлов и последовательно делаем проверки. Сначала смотрим, выбран ли вообще файл. Если нет, добавляем в результирующий массив ошибок пукнт с кодом no_file. Это будет объект-ошибка - имени предсказуемо нет. Дальше мы сразу переходим к следующему файлу, проверит размер и расширение мы не сможем.

Проверка на максимальный размер в случае неуспеха вернет объект , а несоответствие типа -

Обратите внимание, если в одном файле ловится несколько ошибок (максимум две в нашем случае), например, test.pdf слишком большого веса, то в результате мы получим 2 объекта. Имеет смысл группировать эти ошибки по имени файла, но не хочется усложнять код. В конце концов, если это необходимо, Вы сможете обработать выводимый результат как угодно.

Функцию написали, осталось задействовать ее в основном потоке кода. Немного расширим код submitForm:

Как видим, мы добавили вызов validateFiles с нужными параметрами. Максимальный размер файла ограничим 2 Мб, а типы возьмем jpg и png-картинки. После вызова проверяем, не пустой ли массив полученных ошибок, и если таки не пустой, то выводим результаты в консоль и выходим из функции сабмита. Файлы на сервер не уйдут.

Как обрабатывать ошибки и показывать их пользователю - дело исключительно хозяйское :-) Нам сейчас главное убедиться, что наша валидация работает.

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

Валидация файлов на сервере

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

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

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

После этого остается использовать написанную функцию в основном коде

Вот и все. Как видим, валидация на сервере подключена ровно таким же способом, как и на клиенте.

Чтобы убедиться, что и серверная валидация успешно работает, закомментируйте одну строку в main.js


Например, если Вы пропустите первое поле, во второе загрузите pdf-ку, а в третье - валидную картинку, то увидите примерно такую картину


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

А ещё @support запланировал немного изменить диалог выбора файла:

Есть несколько причин, почему @support сделал кастомную реализацию выбора файла:

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

По этому единственный способ передать содержимое файла в скрипт через стандартный диалог выбора файла, это прочитать файл в веб интерфейсе через File API и перенести содержимое в ресурс с типом "строка". Вариант с передачей содержимого файла через глобальную переменную или базу я расскажу в следующий раз.

0_1560630005338_b09d7bad-61f0-48c4-bf26-8225997abb64-изображение.jpg

Для начала создадим проект и добавим в него ресурс file с типом "строка"

0_1560630223096_9f6c6f0a-bddc-4754-a200-acae4400c989-изображение.jpg

выгрузим проект на сервер bablosoft и сгенерируем интерфейс скрипту:

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

0_1560630997962_a77c9a05-2283-4fc2-8504-56cdc565a3d2-изображение.jpg

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

Затем находим строку /////Events в окне Javascript и создаём обработчик события с чтением файла в переменную:

следом находим функцию GetResourceValue, она идёт тремя строками ниже, и добавляем в неё условие для ресурса file

0_1560634186885_87972735-5640-4bf2-a99a-8fba16f820da-изображение.jpg

0_1560632750716_69444a51-f23e-4f9f-b719-b444c260f59b-изображение.jpg

В скрипте всего два действия:

Вот так выглядит результат:

Самый простой вариант применения, это в функции OnApplicationStart распарсить строку по переносу строк \r\n в массив и перенести его в локальный ресурс, чтобы в скрипте использовать как обычный ресурс:

0_1560633082806_76499872-6c1b-4df7-8140-845d093a5241-изображение.jpg


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

Читайте также: