Загрузка файлов с помощью VueJS и Axios
VueJS и Axios GitHub – axios/axios: Promise based HTTP client for the browser and node.js прекрасно работают вместе для выполнения HTTP-запросов. Тем не менее, загрузка файлов с помощью VueJS и Axios может быть немного сложной. Честно говоря, я ненавижу реализовывать загрузку файлов, но это бывает необходимо для большинства приложений. Эта статья может послужить кратким руководством по началу процесса и содержит несколько примеров, которые я изучил при создание загрузки файлов.
Спонсор поста CRM для интернет-торговли
CRM c мессенджерами как единое целое для продаж и обслуживания во всех самых популярных мессенджеров и соц. сетях.
Интеграция с популярными мессенджерами позволяет обхватить максимальное количество источников поступления новых заказов.
Преимущества нашей CRM:
Возможность продаж в соцсетях и мессенджерах.
Вся история предыдущих заказах и коммуникациях.
Возможный чат с клиентом из любого раздела CRM
Наличие Bot API для чат-ботов.
Демо режим.
Попробуйте retailCRM в действии Тариф «Профессиональный» 14 дней бесплатно
Предварительные требования
В этой статье я собираюсь использовать Axios v0.16.2 и VueJS v2.1.0 для реализации загрузки файлов. На бэкэнде вы можете использовать любой фреймворк, который вам нужен, или язык, который вы хотите. Я расскажу об использовании PHP/Laravel для обработки файлов и более или менее псевдокодирования внутреннего процесса. Самое сложное — это реализация загрузки файлов с помощью VueJS и Axios.
Я также предполагаю, что вы используете современный браузер, который поддерживает объект FormData: FormData – Web APIs | MDN. Это то, что делает процесс намного проще.
Загрузка одного файла
Итак, начнем с загрузки одного файла. Для этого я создал простой компонент, который содержит input:
<template> <div class="container"> <div class="large-12 medium-12 small-12 cell"> <label>File <input type="file" id="file" ref="file" v-on:change="handleFileUpload()"/> </label> <button v-on:click="submitFile()">Submit</button> </div> </div> </template>
Если вы посмотрите на input [type=»file»], то там я добавил несколько атрибутов. Первый — это атрибут ref, который дает этому input имя. Теперь мы можем получить доступ к этому input из VueJS, что мы сделаем через секунду.
Следующим является атрибут v-on:change=»handleFileUpload()». Когда пользователь загрузит файл, вызывается этот обработчик. Мы реализуем этот метод на следующем этапе.
Последний элемент в этой простой форме — это кнопка, вызывающая метод submitFile(). В нем мы отправим наш файл на сервер.
Обработка загрузки файла
Первое, что мы сделаем, это добавим метод handleFileUpload() к нашему объекту methods, который даст нам отправную точку для реализации:
<script> export default { methods: { handleFileUpload(){ } } } </script>
В этом методе мы передадим локальной переменной file значение загруженного файла. При использование современного браузера, это будет объект FileList – Web APIs | MDN, который содержит объекты File: File – Web APIs | MDN. FileList не может редактироваться пользователем напрямую из соображений безопасности. Тем не менее, мы можем разрешить пользователям выбирать и отменять выбор файлов по мере необходимости, что мы рассмотрим позже.
Давайте добавим это прямо сейчас к нашему компоненту Vue. В нашем методе data() добавьте:
data(){ return { file: '' } },
Теперь у нас есть что использовать в нашем методе handleFileUpload()! Давайте добавим следующий код в метод handleFileUpload():
methods: { handleFileUpload(){ this.file = this.$refs.file.files[0]; } }
В нем, мы устанавливаем переменную локального file для первого объекта File в FileList в input[type=»file»]. this.$refs.file ссылается на атрибут ref объекта input[type=»file»]. Это делает его легко доступным внутри нашего компонента.
Отправляем файл на сервер через Axios
Теперь пришло время отправить наш файл на сервер через Axios! На нашей кнопке у нас есть метод submitFile(), который мы должны реализовать, поэтому давайте добавим это к нашим методам:
submitFile(){ },
Теперь мы реализуем наш запрос axios.
Первое, что нам нужно сделать, это реализовать объект FormData следующим образом:
let formData = new FormData();
Далее мы добавим файл в форму formData. Это делается с помощью метода append() объекта: FormData.append()(https://developer.mozilla.org/en-US/docs/Web/API/FormData/append). По сути, мы строим пару ключ-значение для отправки на сервер, как стандартный запрос POST:
formData.append('file', this.file);
Мы просто добавляем файловую переменную, в которой хранятся наши данные. Далее, если вы захотите использовать этот код в производство вам нужно будет добавить проверку, чтобы убедиться, что переменная file фактически содержит файл.
Теперь мы можем запустить наш запрос axios! Мы будем делать это через метод post(). Если вы посмотрите на их API (https://github.com/axios/axios#axiosposturl-data-config), вы увидите, что метод post() содержит 3 параметра. Первый параметр — это URL, по которому мы будем отправлять. В нашем примере на сервере настроен URL-адрес /single-file. Следующий параметр — это хранилище ключей и данных, которые мы передаем. Это наша FormData(), которую мы создали, чтобы передать наш файл. Третий параметр — это конфиг для запроса, который позволяет добавить к нему другие заголовки. В нашем случае добавляем заголовок multipart/form-data, который нам нужен для отправки файла на сервер. Без этого заголовка запрос POST будет игнорировать файл.
Наш запрос должен выглядеть следующим образом:
axios.post( '/single-file', formData, { headers: { 'Content-Type': 'multipart/form-data' } } ).then(function(){ console.log('SUCCESS!!'); }) .catch(function(){ console.log('FAILURE!!'); });
В остальной части нашего запроса мы обрабатываем метод обратного вызова для успешного запроса, который можно использовать для отображения уведомления, и мы обрабатываем обратный вызов в случае сбоя, который можно использовать для предупреждения пользователя о неудачной загрузке.
На стороне сервера вы можете получить доступ к файлу через ключ file, который является первым параметром formData.append (‘file’, this.file);.
В PHP это будет: $ _FILES [‘file’], а в Laravel вы можете использовать Request и обращаться к нему через Request::file(‘files’) и выполнять любую обработку на стороне сервера, которая вам нужна.
Наш компонент SingleFile.vue, используемый для тестирования, полностью будет выглядит следующим образом:
<template> <div class="container"> <div class="large-12 medium-12 small-12 cell"> <label>File <input type="file" id="file" ref="file" v-on:change="handleFileUpload()"/> </label> <button v-on:click="submitFile()">Submit</button> </div> </div> </template> <script> export default { data(){ return { file: '' } }, methods: { submitFile(){ let formData = new FormData(); formData.append('file', this.file); axios.post( '/single-file', formData, { headers: { 'Content-Type': 'multipart/form-data' } } ).then(function(){ console.log('SUCCESS!!'); }) .catch(function(){ console.log('FAILURE!!'); }); }, handleFileUpload(){ this.file = this.$refs.file.files[0]; } } } </script>
В следующем разделе мы реализуем загрузку нескольких файлов. В этом нет ничего особенного, но я укажу на изменения!
Загрузка нескольких файлов
Обработка нескольких файлов очень похожа на загрузку одиного файла. Мы начнем с шаблона, который будет выглядеть следующим образом в нашем компоненте Vue:
<template> <div class="container"> <div class="large-12 medium-12 small-12 cell"> <label>Files <input type="file" id="files" ref="files" multiple v-on:change="handleFileUploads()"/> </label> <button v-on:click="submitFiles()">Submit</button> </div> </div> </template>
Помимо изменения имени атрибута ref и изменения идентификатора на files, наиболее важным атрибутом является то, что мы добавили multiple к нашему input[type=»file”]. Это позволит пользователю использовать cmd (ctrl) + клик, чтобы выбрать несколько файлов одновременно. В следующем разделе мы позволим пользователю удалять файлы и выбирать больше файлов, если они допустили ошибку 😉.
Метод загрузки нескольких файлов handleFileUploads()
Он очень похож на загрузку одного файла, за исключением того, что мы добавим все файлы в наш массив, если пользователь выберет более одного. Во-первых, давайте добавим новое хранилище данных в компонент Vue и присвоим этой переменной имя files:
data(){ return { files: '' } },
Теперь у нас есть локальная переменная для хранения наших файлов. Теперь мы можем сделать наш метод handleFileUploads():
handleFilesUpload(){ this.files = this.$refs.files.files; }
Он позволяет получить все файлы из FilesList из нашей загрузки файлов и сохранить их локально.
Реализация метода submitFiles()
Теперь у нас все готово чтобы отправить все наши файлы на сервер! Во-первых, давайте добавим наш метод submitFiles() в массив методов:
submitFiles(){ },
Как и в последнем методе, сначала инициализируем объект FormData():
let formData = new FormData();
Теперь, переберем все выбранные файлы и добавить их в массив files, который мы собираемся отправить на сервер. Массив files будет ключом в объекте formData(), который мы будем отправлять на сервер:
for( var i = 0; i < this.files.length; i++ ){ let file = this.files[i]; formData.append('files[' + i + ']', file); }
Теперь мы готовы отправить наши файлы на сервер через Axios:
axios.post( '/multiple-files', formData, { headers: { 'Content-Type': 'multipart/form-data' } } ).then(function(){ console.log('SUCCESS!!'); }) .catch(function(){ console.log('FAILURE!!'); });
На стороне сервера вы можете получить доступ к файлам через ключ files, который является первым параметром formData.append(‘files[‘ + i + ‘]’, file);.
В PHP это может быть: $_FILES[‘files’], а в Laravel вы можете обращаться к нему через Request::file(‘files’) и выполнять любую обработку на стороне сервера, которая вам нужна. Теперь вы можете просмотреть все файлы, чтобы можно было загружать их несколько раз.
Наш компонент MultipleFiles.vue должен выглядеть следующим образом:
<template> <div class="container"> <div class="large-12 medium-12 small-12 cell"> <label>Files <input type="file" id="files" ref="files" multiple v-on:change="handleFilesUpload()"/> </label> <button v-on:click="submitFiles()">Submit</button> </div> </div> </template> <script> export default { /* Defines the data used by the component */ data(){ return { files: '' } }, methods: { submitFiles(){ let formData = new FormData(); for( var i = 0; i < this.files.length; i++ ){ let file = this.files[i]; formData.append('files[' + i + ']', file); } axios.post( '/multiple-files', formData, { headers: { 'Content-Type': 'multipart/form-data' } } ).then(function(){ console.log('SUCCESS!!'); }) .catch(function(){ console.log('FAILURE!!'); }); }, handleFilesUpload(){ this.files = this.$refs.files.files; } } } </script>
Следующим шагом мы позволим пользователям редактировать файлы, которые они выбрали, чтобы они случайно не загрузили файл, который им не нужен.
Разрешение пользователям редактировать выбранные файлы перед загрузкой
При загрузке нескольких файлов вы можете случайно выбирать файл, который вы НЕ захотите загружать. Это кажется достаточно простым для реализации, пока вы не обнаружите, что не можете напрямую редактировать объект FileList по соображениям безопасности. Однако вы можете преобразовать его и отредактировать новый список в виде массива и позволить пользователям изменять файлы, которые они хотят загрузить.
Во-первых, давайте повторно используем шаблон из компонента MultipleFiles.vue:
<template> <div class="container"> <div class="large-12 medium-12 small-12 cell"> <label>Files <input type="file" id="files" ref="files" multiple v-on:change="handleFilesUpload()"/> </label> <button v-on:click="submitFiles()">Submit</button> </div> </div> </template>
Скрываем input
Первое, что мы сделаем, это скроем input. Для того чтобы сделать более простым дизайн интерфейса, чтобы пользователям было удобнее выбирать нужные им файлы. Для этого я просто добавил тег стиля, который убирает input с экрана.
<style> input[type="file"]{ position: absolute; top: -500px; } </style>
Затем я добавил кнопку, которая вызывает щелчок на input:
<div class="large-12 medium-12 small-12 cell"> <button v-on:click="addFiles()">Add Files</button> </div>
Поэтому, когда мы кликаем на кнопке, мы запускаем клик на элементе input. Далее нам нужно реализовать метод addFiles() в нашем компоненте Vue следующим образом:
addFiles(){ this.$refs.files.click(); }
Это вызовет клик на input, и пользователю будет показано окно выбора файлов, где они смогут выбрать нужные файлы.
Реализация handleFilesUpload()
Здесь все становится немного иначе. С начало как и в первых двух примерах, мы добавим локальную переменную для добавления файлов:
data(){ return { files: [] } },
Теперь, когда пользователь выбирает некоторые файлы для загрузки, мы помещаем их в нашу локальную переменную files:
let uploadedFiles = this.$refs.files.files; for( var i = 0; i < uploadedFiles.length; i++ ){ this.files.push( uploadedFiles[i] ); }
Мы делаем это через цикл, а не помещаем весь кусок в массив files, потому что в противном случае у нас были бы группы, основанные на том, что было выбрано. Здесь вы также можете добавить проверки, чтобы пользователь не загружал один и тот же файл несколько раз.
Отображение загруженных файлов
В случае если мы хотим, чтобы пользователи могли удалить выбранные файлы, нам нужно отобразить загруженные в данный момент файлы.
Для этого мы вернемся к нашему шаблону и добавим следующий код:
<div class="large-12 medium-12 small-12 cell"> <div v-for="(file, key) in files" class="file-listing">{{ file.name }} <span class="remove-file" v-on:click="removeFile( key )">Remove</span></div> </div> <br>
Это делается для перебора всех файлов, которые мы добавили в данный момент, и отображения их пользователю.
Стоит отметить несколько вещей.
Во-первых, v-for=»(file, key) in files» перебирает файлы, которые мы загрузили, и получает ключ, который является индексом файла в массиве файлов и самого файла. Затем мы отображаем имя файла через: {{file.name}}, который является частью отдельного объекта файла. В объекте содержится дополнительная информация, которая описана здесь: File – Web APIs | MDN (https://developer.mozilla.org/en-US/docs/Web/API/File)
Далее мы добавляем метод removeFile(key), который удалит файл из массива файлов. Когда файл будет удален, реактивная природа VueJS обновит наш список.
Реализация метода removeFile()
Этот метод удалит файл из нашего массива загруженных файлов. Во-первых, давайте добавим метод в наш массив методов:
removeFile( key ){ }
Метод принимает ключ из массива файлов указывающий на удаляемый файл. Полная реализация этого метода будет такая:
removeFile( key ){ this.files.splice( key, 1 ); }
Он соединяет массив файлов с индексом удаляемого нами файла и удаляет одну запись из массива. Когда мы сделаем это, наш список будет перерисован через VueJS. Поскольку мы используем массив локальных files, мы можем изменить его по своему желанию. Следующее и последнее, что мы должны сделать, это отправить наши файлы на сервер, который выбрал пользователь!
Отправка файлов на сервер
Далее отправим выбранные файлы на сервер для обработки.
Для этого добавим метод submitFiles() к нашему объекту methods:
submitFiles(){ }
Как и в остальных примерах, давайте сначала создадим наш объект FormData():
let formData = new FormData();
Теперь давайте добавим все выбранные файлы в данные формы formData:
for( var i = 0; i < this.files.length; i++ ){ let file = this.files[i]; formData.append('files[' + i + ']', file); }
Это перебирает файлы, которые выбрал пользователь, и подготавливает их для отправки на сервер.
Теперь мы можем запустить метод axios.post() для отправки файлов в нашу конечную точку:
axios.post( '/select-files', formData, { headers: { 'Content-Type': 'multipart/form-data' } } ).then(function(){ console.log('SUCCESS!!'); }) .catch(function(){ console.log('FAILURE!!'); });
Это отправляет все наши данные формы на сервер с файлами, которые пользователь загрузил! Если вы запустите это в качестве примера, вы увидите, что после удаления файла он больше не отправляется на сервер.
Как и раньше, на стороне сервера вы можете получить доступ к файлам через ключ files, который является первым параметром formData.append(‘files[‘ + i + ‘]’, file);.
При использовании Laravel и Request вы можете получить доступ к выбранным файлам, загруженным пользователем, следующим способом: Request::file(‘files’). В простом PHP это будет $_FILES[‘files’]. Теперь вы можете делать любую обработку, какую захотите!
Наш компонент SelectFiles.vue должен выглядеть следующим образом:
<style> input[type="file"]{ position: absolute; top: -500px; } div.file-listing{ width: 200px; } span.remove-file{ color: red; cursor: pointer; float: right; } </style> <template> <div class="container"> <div class="large-12 medium-12 small-12 cell"> <label>Files <input type="file" id="files" ref="files" multiple v-on:change="handleFilesUpload()"/> </label> </div> <div class="large-12 medium-12 small-12 cell"> <div v-for="(file, key) in files" class="file-listing">{{ file.name }} <span class="remove-file" v-on:click="removeFile( key )">Remove</span></div> </div> <br> <div class="large-12 medium-12 small-12 cell"> <button v-on:click="addFiles()">Add Files</button> </div> <br> <div class="large-12 medium-12 small-12 cell"> <button v-on:click="submitFiles()">Submit</button> </div> </div> </template> <script> export default { data(){ return { files: [] } }, methods: { addFiles(){ this.$refs.files.click(); }, submitFiles(){ let formData = new FormData(); for( var i = 0; i < this.files.length; i++ ){ let file = this.files[i]; formData.append('files[' + i + ']', file); } axios.post( '/select-files', formData, { headers: { 'Content-Type': 'multipart/form-data' } } ).then(function(){ console.log('SUCCESS!!'); }) .catch(function(){ console.log('FAILURE!!'); }); }, handleFilesUpload(){ let uploadedFiles = this.$refs.files.files; for( var i = 0; i < uploadedFiles.length; i++ ){ this.files.push( uploadedFiles[i] ); } }, removeFile( key ){ this.files.splice( key, 1 ); } } } </script>
Вот и все! Теперь вы можете разрешить пользователям исправлять свои ошибки, если они выберут файл, который не захотят загружать.
Несколько советов
Я бы хотел добавить еще несколько советов, на которые можно обратить внимание при загрузке с использованием FormData.
Добавление дополнительных данных в POST запрос
Вы всегда можете включить в свой POST запрос больше информации, чем простые файлы. Когда вы создаете свои FormData, вы можете добавить дополнительный текст или другие поля, например:
let formData = new FormData(); for( var i = 0; i < this.files.length; i++ ){ let file = this.files[i]; formData.append('files[' + i + ']', file); } formData.append('first_name', 'Dan'); formData.append('last_name', 'Pastori');
Поля first_name и last_name будут доступны на сервере, как обычный post запрос!
Массивы с FormData()
Теперь, поскольку мы настраиваем наш запрос перед отправкой на сервер, доступ к массивам осуществляется по-разному. Вам нужно будет учесть это при создании объекта FormData. В VueJS при работе с массивами вы не можете просто использовать:
this.coolData = ['one', 'two', 'three']; formData.append('cool_data', this.coolData);
так как вы получите [object Object] на стороне сервера. Вы можете либо перебирать свои данные и помещать их в специально организованный массив, либо вы можете использовать метод JSON.stringify() для данных, который преобразует их в JSON перед отправкой на сервер.
this.coolData = ['one', 'two', 'three']; formData.append('cool_data', JSON.stringify( this.coolData ) );
Вам просто нужно будет расшифровать его, прежде чем вы сможете получить к нему доступ. В PHP для этого есть метод json_decode($ json).
Очистка локальных файлов
Когда axios возвращает успех после загрузки данный, нужно сбросить массив локальных файлов обратно в null. Это гарантирует пользователю, который первоначально отправил файлы, и попытается повторно отправить другую группу файлов, что бы он не получил первую группу файлов, которая все еще существуют в массиве. Вы можете просто очистить ваши локальные файлы следующим образом:
this.files = [];
Заключение
Надеюсь, эта статья немного поможет вам в реализация загрузки файлов с помощью VueJS и Axios через AJAX.
Оригинальная статья: Dan Pastori — Uploading Files With VueJS and Axios
Все прекрасно, но не понял почему он инпут спрятал на верху а не просто скрыл и почему клик по нему сделал функцией когда можно было просто label поставить. По мне так просто усложнил в этом случае.
В общем статья понравилась ))
Наверно просто так (так автору захотелось).
Дошёл до места «В нашем примере на сервере настроен URL-адрес /single-file.» и… никаких пояснений. И так почти во всех статьях на эту тему. Неужели нельзя было пояснить как на сервере произвести настройку?? В итоге статья заводит в тупик.
Это зависит от того, что на сервере обрабатывает запросы. Там и php может быть, и java, и nodeJS.
Все хорошо, но почему же сразу не показать и валидацию загружаемого файла, по расширению и размеру?
Спасибо, большое!
Хорошая статья, но везде в сети описывается только добавление файла. А вот как его передать при редактировании (put при update)?