Загрузка файлов на сервер нативным JavaScript при помощи formData асинхронно (без перезагрузки страницы)

от 2019 - 01 - 22

Статья для тех, кто планирует загружать свои файлы на сервер используя JavaScript в 2019-ом году. Ныне в наших руках есть удобные инструменты (API — application programming interface), позволяющие, не повредившись головой, при минимальных временных затратах реализовать загрузку файлов на сервер. В данном уроке будет продемонстрировано создание фронт части кода, то есть тот скрипт, который отработает на пользовательской стороне (на устройстве пользователя). В браузере посетителя вашего сайта. С поддержкой drag-and-drop. С отображением процесса загрузки. Используя стандарты ES2015 и соответствующие события с объектами, такими, как XMLHttpRequest (существует для генерации асинхронных запросов к серверу — ajax) и FormData (позволяет удобно и быстро собирать данные из форм).

(В будущем я покажу тебе, как принимать отправленные пользователем файлы на стороне сервера, мы напишем PHP скрипт, со всеми необходимыми проверками. Тут будет ссылка.)

Полезная статья, ориентируясь на которую была написана эта, находится тут, на MDN ( Mozilla Develeopers Network )

Итак, с чего начать?
Начать следует с создания html вёрстки той области, при перетаскивании на которую иконки файла будут срабатывать drag-and-drop события браузера.
У меня этот код выглядит так:

<div id="drug-and-drop-media" class="d-flex flex-column justify-content-center">
	Медийный контент<br>
	<input type="file" id="fileElem" multiple accept="image/*" onchange="handleFiles(this.files)">
	<label class="button align-self-center" for="fileElem">Выбрать файл</label>
</div>

CSS стили, для этого блока я оставляю на ваше усмотрение. Единственное, отмечу вне гласный договор обрамлять область поддерживающую drag-and-drop пунктирном. А так же то, что непосредственно сам input формы, в котором разместятся пути и имена файлов для загрузки, я прячу — display: none; А label, связанный с инпутом, мы стилизуем под кнопку загрузки. При клике по которой появится стандартное диалоговое окно выбора файлов с вашего устройства.

После чего можно заняться написанием JavaScript кода с логикой:

// Drag-and-drop functions
let dropArea = document.getElementById('drug-and-drop-media');

['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
	dropArea.addEventListener(eventName, preventDefaults, false);
});

function preventDefaults (e) {
  e.preventDefault();
  e.stopPropagation();
}

['dragenter', 'dragover'].forEach(eventName => {
	dropArea.addEventListener(eventName, highlight, false);
});

['dragleave', 'drop'].forEach(eventName => {
	dropArea.addEventListener(eventName, unhighlight, false);
});

function highlight(e) {
	dropArea.classList.add('highlight');
}
function unhighlight(e) {
	dropArea.classList.remove('highlight');
}

У первых строках моего скрипта мы получим, по ID, DOM (Document Object Model — объект html документа) объект самой области для загрузки. После чего на все доступные вам события (этих событий больше, но остальные нам не нужны сейчас, если интересно, почитайте оригинальную статью, ссылка выше) мы добавим дополнительную функцию preventDefaults, которая будет останавливать выполнение этих событий и их пузырьковое всплытие к родительским элементам, вплоть до body и html а дальше браузер уже попытается открыть загружаемый файл. Что для нас лишнее. Выполнение и всплытие нам следует тормозить и прекращать при перехвате.

Далее добавлены обработчики событий — «файл проносят над областью для загрузки», «курсор с иконкой файла покинул область или файл в область успешно брошен». В моём случае будут запускаться соответственно функции добавления/удаления определённого css класса highlight, который ответственен за стилизацию блока. Что бы красиво подсвечивать его, информируя пользователя о том, что он на верном пути.

Вот мы и добрались до самого интересного — обработка события успешного размещения файла в drag and drop область и последующая загрузка его на сервер.

dropArea.addEventListener('drop', handleDrop, false);
function handleDrop(e) {
	let dt = e.dataTransfer;
	let files = dt.files;
	handleFiles(files);
}

function handleFiles( files ) {
	files.forEach(uploadFile);
}

function uploadFile( file ) {
	
	var url = 'ВАШ URL ДЛЯ ЗАГРУЗКИ ФАЙЛОВ';
	var xhr = new XMLHttpRequest();
	var formData = new FormData();
	
	xhr.open('POST', url, true);
	xhr.addEventListener('readystatechange', function(e) {
		if (xhr.readyState == 4 && xhr.status == 200) {
		  // Готово. Информируем пользователя
		}
		else if (xhr.readyState == 4 && xhr.status != 200) {
		  // Ошибка. Информируем пользователя
		}
	});
	
	formData.append('file', file);
	xhr.send( formData );
}
///