Drag and Drop завантаження файлів на сервер

17

Від автора: ця стаття написана нашим гостем Osvaldas Valutis. Osvaldas розповість нам не лише про drag and drop завантаження файлів на сервер, але і торкнеться теми UI і UX, підтримки браузерів, а також покаже, як реалізувати дану завантаження з допомогою методу прогресивного поліпшення.

Зараз я працюю над RSS рідером Readerrr. І переді мною стояло завдання урізноманітнити звичайний спосіб додавання файлів через input, я хотів реалізувати drag and drop модель. Іноді такий спосіб набагато зручніше, хіба не так?

Drag and Drop завантаження файлів на сервер

Демо

Розмітка

В розмітці немає нічого особливого. Вона звичайна, просто тег form, хоча присутні також блоки з потенційними станами.

Choose a file or drag it here.
Завантаження…
Done!
Error! .

Поки блоки станів нам не потрібні, вони ховаються:

.box__dragndrop,
.box__uploading,
.box__success,
.box__error {
display: none;
}

Розбираємо код:

По станам: .box__uploading з’являється під час завантаження файлу через Ajax (всі інші блоки станів приховані). Потім в залежності від результату завантаження відображаються .box__success або .box__error.

input[type=»file»] label – функціональні елементи форми. У статті настроювання input’ів типу file я описував, як їх можна стилізувати. Також в тій статті я розповідав, навіщо потрібний атрибут [data-multiple-caption]. Input і label служать альтернативою звичайному способу вибору файлів (або єдиний спосіб, якщо drag and drop не підтримується).

.box__dragndrop відображається, якщо браузер підтримує drag and drop.

Виявлення властивостей

Ми не можемо 100% покладатися на підтримку в браузері drag and drop. Необхідно забезпечити фолбек, в цьому нам допоможе метод виявлення підтримуваних властивостей. В основі методу drag and drop лежать безліч різних JavaScript API, необхідно перевірити їх всі.

Спочатку сама подія drag & drop. Роботу по виявленню підтримуваних властивостей можна з упевненістю довірити бібліотеці Modernizr. Нижче тест події:

var div = document.createElement(‘div’);
return (‘draggable’ in div) || (‘ondragstart’ in div && ‘ondrop’ in div)

Далі необхідно перевірити FormData інтерфейс. Даний інтерфейс формує програмний об’єкт вибраного файлу (ів), після чого він (і) можуть бути відправлені на сервер через Ajax:

return ‘FormData’ in window;

Також необхідно перевірити об’єкт DataTransfer. Тут трохи хитрий метод перевірки, так як не існує ще стовідсоткового способу перевірки на підтримку даного об’єкта до того, як користувач не почне взаємодіяти з drag and drop інтерфейсом. Не всі браузери підтримують об’єкт. Взагалі, необхідно уникати таких UX моментів типу:

«Перетягни файл сюди!»

Користувач перетягнув і відпустив файл і

«Опа, я пожартував, така функція не підтримується.»

Основне завдання встигнути перевірити підтримку FileReader API в момент завантаження документа. Сенс в тому, що якщо браузер підтримує FileReader, то і DataTransfer він теж підтримує:

‘FileReader’ in window

Додамо код вище в анонімну самовызывающуюся функцію…

var isAdvancedUpload = function() {
var div = document.createElement(‘div’);
return ((‘draggable’ in div) || (‘ondragstart’ in div && ‘ondrop’ in div)) && ‘FormData’ in window && ‘FileReader’ in window;
}();

…з її допомогою можна чітко визначити підтримку властивостей:

if (isAdvancedUpload) {
// …
}

З повністю робочим методом виявлення підтримуваних властивостей, ми можемо сказати користувачеві, чи може він скористатися drag and drop чи ні. У разі підтримки до форми можна додати спеціальний клас, щоб потім стилізувати форму:

var $form = $(‘.box’);
if (isAdvancedUpload) {
$form.addClass(‘has-advanced-upload’);
}
.box.has-advanced-upload {
background-color: white;
outline: 2px dashed black;
outline-offset: -10px;
}
.box.has-advanced-upload .box__dragndrop {
display: inline;
}

Drag and Drop завантаження файлів на сервер

Не біда якщо drag & drop не підтримується. Користувачі можуть завантажувати файли через старий добрий input[type=»file»]!

Drag and Drop завантаження файлів на сервер

Зверніть увагу: в Microsoft Edge є баг, після якого drag & drop не працює. Розробники начебто вже знають про нього, і працюють над виправленням.

Drag ‘n’ Drop

А тепер займемося справою. У цій частині ми будемо додавати до форми або видаляти спеціальні класи станів типу, коли користувач тримає елемент над формою. Коли користувач буде відпускати кнопку миші, ми будемо ловити дані файли.

if (isAdvancedUpload) {
var droppedFiles = false;
$form.on(‘drag dragstart dragend dragover dragenter dragleave drop’, function(e) {
e.preventDefault();
e.stopPropagation();
})
.on(‘dragover dragenter’, function() {
$form.addClass(‘is-dragover’);
})
.on(‘dragleave dragend drop’, function() {
$form.removeClass(‘is-dragover’);
})
.on(‘drop’, function(e) {
droppedFiles = e.originalEvent.dataTransfer.files;
});
}

e.preventDefault() і e.stopPropagation() запобігають будь-які небажані дії конкретних подій у браузері.

e.originalEvent.dataTransfer.files повертає список файлів. Пізніше я покажу, як використовувати цю інформацію для відправлення файлів на сервер.

З допомогою класу .is-dragover ми будемо вказувати користувачу, коли можна відпустити файли:

Drag and Drop завантаження файлів на сервер

Стандартний спосіб вибору файлів

Іноді drag & drop не самий зручний спосіб вибору файлів. Особливо якщо у користувача маленький екран. А значить, необхідно дати користувачеві вибір між різними методами завантаження. В цьому нам допоможе input типу file і label. Стилізувавши їх описаним мною способом, можна зберегти цілісність дизайну:

Drag and Drop завантаження файлів на сервер

Ajax завантаження

Не існує повністю кроссбраузерного способу реалізувати drag & drop без Ajax. Деякі браузери IE і Firefox) не дозволяють встановлювати значення в input’ах типу file, які потім можуть вирушати на сервер. Код нижче не працює:

$form.find(‘input[type=»file»]’).prop(‘files’, droppedFiles);

Замість коду вище після відправки форми необхідно використовувати Ajax:

$form.on(‘submit’, function(e) {
if ($form.hasClass(‘is-uploading’)) return false;
$form.addClass(‘is-uploading’).removeClass(‘is-error’);
if (isAdvancedUpload) {
// ajax для сучасних браузерів
} else {
// ajax для старих браузерів
}
});

У класу .is-uploading подвійне значення: він запобігає повторну відправку форми (return false), а також показує користувачам процес відправки форми:

.box.is-uploading .box__input {
visibility: none;
}
.box.is-uploading .box__uploading {
display: block;
}

Ajax для сучасних браузерів

Якщо б у формі не було завантаження файлів, то нам би і не знадобилося два різних Ajax способу. Але на жаль, в IE9 і нижче не підтримується завантаження через XMLHttpRequest.

Щоб зрозуміти, який метод підтримується, можна скористатися нашим готовим тестом isAdvancedUpload. Так як якщо браузер підтримує те, про що я писав вище, то він буде підтримувати завантаження через XMLHttpRequest. Код нижче працює в IE10+:

if (isAdvancedUpload) {
e.preventDefault();
var ajaxData = new FormData($form.get(0));
if (droppedFiles) {
$.each( droppedFiles, function(i, file) {
ajaxData.append( $input.attr(‘name’), file );
});
}
$.ajax({
url: $form.attr(‘action’),
type: $form.attr(‘method’),
data: ajaxData,
dataType: ‘json’,
cache: false,
contentType: false,
processData: false,
complete: function() {
$form.removeClass(‘is-uploading’);
},
success: function(data) {
$form.addClass( data.success == true ? ‘is-success’ : ‘is-error’ );
if (!data.success) $errorMsg.text(data.error);
},
error: function() {
// Зберігайте помилки в логи, показуйте попередження, що завгодно
}
});
}

FormData($form.get(0)) збирає дані з усіх input’ів.

Цикл $.each() пробігає по всьому скинутим файлів. ajaxData.append() додає ці файли в стек для відправки через Ajax.

data.success і data.error – JSON рядки з результатом виконання, повернуті сервером. Нижче показано, як це буде виглядати на PHP:

$is_success, ‘error’=> $error_msg]));
?>

Ajax для старих браузерів

Метод для IE9-. Нам не потрібно збирати drag & drop файли (isAdvancedUpload = false), так як даний метод не підтримується браузером. Форма працює через input[type=»file»]. Як не дивно, динамічна вставка iframe працює:

if (isAdvancedUpload) {
// …
} else {
var iframeName = ‘uploadiframe’ + new Date().getTime();
$iframe = $(‘‘);
$(‘body’).append($iframe);
$form.attr(‘target’, iframeName);
$iframe.one(‘load’, function() {
var data = JSON.parse($iframe.contents().find(‘body’ ).text());
$form
.removeClass(‘is-uploading’)
.addClass(data.success == true ? ‘is-success’ : ‘is-error’)
.removeAttr(‘target’);
if (!data.success) $errorMsg.text(data.error);
$form.removeAttr(‘target’);
$iframe.remove();
});
}

Автоматична відправка файлів

Якщо на вашій формі тільки drag & drop полі або input типу file, для зручності користувача можна зробити, щоб файли автоматично завантажувались на сервер без натискання на кнопку відправити. Для цього необхідно вручну запустити подія submit:

// …
.on(‘drop’, function(e) { // drag & drop підтримується
droppedFiles = e.originalEvent.dataTransfer.files;
$form.trigger(‘submit’);
});
// …
$input.on(‘change’, function(e) { // drag & drop НЕ підтримується
$form.trigger(‘submit’);
});

Якщо добре спроектувати область drag & drop (користувачеві буде очевидно, що від нього хочуть), можна взагалі сховати кнопку відправки (іноді, чим менше інтерфейсу, тим краще). Але будьте обережні. Якщо з якихось причин JavaScript відключений, кнопка повинна бути видимою (прогресивне поліпшення!). Щоб зрозуміти, включений JS, можна скористатися класом .no-js в тегу html:

(function(e,t,n){var r=e.querySelectorAll(«html»)[0];r.className=r.className.replace(/(^|\s)no-js(\s|$)/,»$1js$2″)})(document,window,0);
.box__button {
display: none;
}
.no-js .box__button {
display: block;
}

Відображення вибраних файлів

Якщо ви не використовуєте метод автоотправки файлів на сервер, то необхідно показати користувачеві, що він успішно вибрав файли для завантаження:

var $input = $form.find(‘input[type=»file»]’),
$label = $form.find(‘label’),
showFiles = function(files) {
$label.text(files.length > 1 ? ($input.attr(‘data-multiple-caption’) || «).replace( ‘{count}’, files.length ) : files[ 0 ].name);
};
// …
.on(‘drop’, function(e) {
droppedFiles = e.originalEvent.dataTransfer.files; // the files that were dropped
showFiles( droppedFiles );
});
//…
$input.on(‘change’, function(e) {
showFiles(e.target.files);
});

Drag and Drop завантаження файлів на сервер

Drag and Drop завантаження файлів на сервер

Коли відключений JavaScript

Основний принцип прогресивного поліпшення в тому, що користувач повинен у будь способів закінчити принципову завдання, неважливо яким. І завантаження файлів не виняток. Якщо з якихось причин JavaScript не доступний, інтерфейс повинен виглядати так:

Drag and Drop завантаження файлів на сервер

При натисканні на кнопку «надіслати», сторінка оновиться. Так як JS відключений, то з його допомогою можна показати результат відправки. Тут необхідно покластися на сервер. Нижче показаний приклад:

І трохи правок у розмітці:

form class=»box» method=»post» action=»» enctype=»multipart/form-data»>

Done!
Error! .

От і все! Ця і так довга стаття могла бути ще довшим. Але я думаю, цього цілком вистачить, щоб ви почали використовувати адаптивну drag & drop завантаження в своїх проектах. Щоб більш детально ознайомитися з принципом роботи, вивчіть демо (в исходниках можна подивитися no-jQuery залежності).