Статьи

Как создать форму загрузки файла с помощью Express и Dropzone.js

Посмотрим правде в глаза, никто не любит формы. Разработчикам не нравится создавать их, дизайнерам не особенно нравится их стилизация, а пользователям, конечно, не нравится их заполнять.

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

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

Мы рассмотрим DropzoneJS более подробно; покажите, как его реализовать, и посмотрите, как его можно настроить и настроить. Мы также реализуем простой механизм загрузки на стороне сервера, используя Node.js.

Как всегда, вы можете найти код этого руководства в нашем репозитории GitHub .

Представляем DropzoneJS

Как видно из названия, DropzoneJS позволяет пользователям загружать файлы, используя drag n ‘drop. Хотя преимущества юзабилити можно обоснованно обсуждать , это все более распространенный подход, который согласуется с тем, как многие люди работают с файлами на своем рабочем столе. Это также довольно хорошо поддерживается во всех основных браузерах.

Однако DropzoneJS — это не просто виджет, основанный на перетаскивании; щелчок по виджету запускает более традиционный подход выбора файлов.

Вот анимация виджета в действии:

Виджет DropzoneJS в действии

В качестве альтернативы, посмотрите на этот, самый минимальный из примеров .

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

Характеристики

Чтобы суммировать некоторые функции и характеристики плагина:

  • Может использоваться с или без jQuery
  • Перетащите поддержку
  • Создает миниатюрные изображения
  • Поддерживает несколько загрузок, опционально параллельно
  • Включает индикатор выполнения
  • Полностью тема
  • Расширенная поддержка проверки файлов
  • Доступен как модуль AMD или модуль RequireJS
  • При минимизации он достигает 33Кб

Поддержка браузера

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

  • Chrome 7+
  • Firefox 4+
  • IE 10+
  • Opera 12+ (версия 12 для MacOS отключена, потому что их API глючит)
  • Safari 6+

Есть несколько способов справиться с запасными вариантами, когда плагин поддерживается не полностью, и мы рассмотрим это позже.

Установка

Самый простой способ установить DropzoneJS через Bower:

bower install dropzone 

В качестве альтернативы вы можете получить его с Github или просто загрузить отдельный файл JavaScript — хотя имейте в виду, что вам также понадобятся базовые стили , также доступные в репозитории Github.

Существуют также сторонние пакеты, предоставляющие поддержку ReactJS и реализующие виджет в виде директивы Angular .

Первые шаги

Если вы использовали метод Bower или метод загрузки, убедитесь, что вы включили как основной файл JS, так и стили (или включили их в таблицу стилей вашего приложения), например:

 <link rel="stylesheet" href="/path/to/dropzone.css"> <script type="text/javascript" src="/path/to/dropzone.js"></script> 

Предварительно минимизированные версии также поставляются в упаковке.

Совет: если вы используете RequireJS, используйте взамен dropzone-amd-module.js .

Основное использование

Самый простой способ реализовать плагин — это прикрепить его к форме, хотя вы можете использовать любой HTML, например тег div . Использование формы, однако, означает меньшее количество параметров для установки — прежде всего, URL, который является наиболее важным свойством конфигурации.

Вы можете инициализировать его, просто добавив класс dropzone , например:

 <form id="upload-widget" method="post" action="/upload" class="dropzone"> </form> 

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

 Dropzone.options.WIDGET_ID = { // }; 

Чтобы получить идентификатор виджета для настройки параметров, возьмите идентификатор, который вы определили в своем HTML-коде, и верблюжий кейс. Например, upload-widget становится uploadWidget :

 Dropzone.options.uploadWidget = { // }; 

Вы также можете создать экземпляр программно:

 var uploader = new Dropzone('#upload-widget', options); 

Далее мы рассмотрим некоторые из доступных вариантов конфигурации.

Основные параметры конфигурации

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

Опция method устанавливает метод HTTP, и, опять же, он будет извлекать его из элемента формы, если вы используете этот подход, иначе он просто по умолчанию будет использовать POST , что должно соответствовать большинству сценариев.

Параметр paramName используется для установки имени параметра для загружаемого файла; Если вы используете элемент формы загрузки файла, он будет соответствовать атрибуту name . Если вы не включите его, то по умолчанию это file .

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

По умолчанию виджет будет отображать диалоговое окно файла при щелчке, хотя вы можете использовать параметр clicked чтобы отключить его, установив для него значение false , или в качестве альтернативы вы можете предоставить элемент HTML или CSS-селектор для настройки элемента clickable.

Это основные варианты, но давайте теперь посмотрим на некоторые из более сложных вариантов.

Обеспечение максимального размера файла

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

Ограничение для определенных типов файлов

Параметр acceptFiles можно использовать для ограничения типа файла, который вы хотите принять. Это должно быть в форме списка MIME-типов, разделенных запятыми, хотя вы также можете использовать подстановочные знаки.

Например, чтобы принимать только изображения:

 acceptedFiles: 'image/*', 

Изменение размера миниатюры

По умолчанию миниатюра генерируется с разрешением 120 на 120 пикселей; то есть это квадрат. Есть несколько способов изменить это поведение.

Во-первых, использовать параметры конфигурации thumbnailWidth и / или thumbnailHeight .

Если вы установите для обоих thumbnailWidth и thumbnailHeight значение null , миниатюра не будет изменена вообще.

Если вы хотите полностью настроить поведение генерации миниатюр, вы можете даже переопределить функцию resize .

Один важный момент об изменении размера миниатюры; класс dz-image предоставляемый пакетом, устанавливает размер миниатюры в CSS, поэтому вам также необходимо изменить его соответствующим образом.

Дополнительные проверки файлов

Опция accept позволяет вам предоставить дополнительные проверки, чтобы определить, является ли файл действительным, прежде чем он будет загружен. Вам не следует использовать это для проверки количества файлов ( maxFiles ), типа файла ( maxFilesize ) или размера файла ( maxFilesize ), но вы можете написать собственный код для выполнения других видов проверки.

Вы бы использовали опцию accept как это:

 accept: function(file, done) { if ( !someCheck() ) { return done('This is invalid!'); } return done(); } 

Как вы можете видеть, это асинхронно; Вызовите done() без аргументов и пропусков проверки или предоставьте сообщение об ошибке, и файл будет отклонен, отображая сообщение вместе с файлом как всплывающее окно.

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

Отправка дополнительных заголовков

Часто вам нужно прикрепить дополнительные заголовки к HTTP-запросу загрузчика.

Например, один из подходов к защите CSRF (подделка межсайтовых запросов) — вывести токен в представлении, а затем попросить конечные точки POST/PUT/DELETE проверить заголовки запроса на наличие действительного токена. Предположим, вы вывели свой токен так:

 <meta name="csrf-token" content="CL2tR2J4UHZXcR9BjRtSYOKzSmL8U1zTc7T8d6Jz"> 

Затем вы можете добавить это в конфигурацию:

 headers: { 'x-csrf-token': document.querySelectorAll('meta[name=csrf-token]')[0].getAttributeNode('content').value, }, 

Кроме того, вот тот же пример, но с использованием jQuery:

 headers: { 'x-csrf-token': $('meta[name="csrf-token"]').attr('content') }, 

Затем ваш сервер должен проверить заголовок x-csrf-token , возможно, с использованием некоторого промежуточного программного обеспечения.

Обработка откатов

Самый простой способ реализовать запасной вариант — вставить <div> в вашу форму, содержащую элементы управления вводом, установив имя класса для элемента как fallback . Например:

 <form id="upload-widget" method="post" action="/upload" class="dropzone"> <div class="fallback"> <input name="file" type="file" /> </div> </form> 

В качестве альтернативы, вы можете предоставить функцию, которая будет выполняться, когда браузер не поддерживает плагин, используя fallback параметр конфигурации.

Вы можете заставить виджет использовать аварийное поведение, установив для forceFallback значение true , что может помочь при разработке.

Обработка ошибок

Вы можете настроить способ обработки ошибок виджетом, предоставив пользовательскую функцию с помощью параметра конфигурации error . Первый аргумент — это файл, второе сообщение об ошибке, а если ошибка произошла на стороне сервера, то третий параметр будет экземпляром XMLHttpRequest .

Как всегда, проверка на стороне клиента — это только полдела. Вы также должны выполнить проверку на сервере. Когда мы позже реализуем простой компонент на стороне сервера, мы рассмотрим ожидаемый формат ответа об ошибке, который при правильной настройке будет отображаться так же, как и ошибки на стороне клиента (показано ниже).

Отображение ошибок с DropzoneJS

Переопределение сообщений и перевода

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

В частности, dictDefaultMessage используется для установки текста, который появляется в середине рабочей зоны, до того, как кто-то выберет файл для загрузки.

Вы найдете полный список настраиваемых строковых значений — все из которых начинаются с dictв документации .

События

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

Есть два способа прослушать событие. Первый — создать слушателя в функции инициализации:

 Dropzone.options.uploadWidget = { init: function() { this.on('success', function( file, resp ){ ... }); }, ... }; 

Или альтернативный подход, который полезен, если вы решите создать экземпляр Dropzone программно:

 var uploader = new Dropzone('#upload-widget'); uploader.on('success', function( file, resp ){ ... }); 

Возможно, наиболее заметным является событие success , которое запускается, когда файл был успешно загружен. Обратный вызов success принимает два аргумента; первый — объект файла, а второй — экземпляр XMLHttpRequest .

Другие полезные события включают addedfile файл и удаленный файл для addedfile когда файл был добавлен или удален из списка загрузки, thumbnail которая запускается после создания миниатюры, и uploadprogress которую вы можете использовать для реализации собственного индикатора выполнения.

Существует также множество событий, которые принимают объект события в качестве параметра и который можно использовать для настройки поведения самого виджета — drop , drop , dragend , dragenter , dragover и dragleave .

Полный список событий вы найдете в соответствующем разделе документации .

Более сложный пример проверки: размеры изображения

Ранее мы рассматривали асинхронную опцию accept() , которую вы можете использовать для проверки (проверки) файлов перед их загрузкой.

Общее требование при загрузке изображений — обеспечить минимальный или максимальный размер изображения. Мы можем сделать это с DropzoneJS, хотя это немного сложнее.

Хотя обратный вызов accept принимает файловый объект, для проверки размеров изображения нам нужно подождать, пока миниатюра не будет сгенерирована, и в этот момент размеры будут установлены для файлового объекта. Для этого нам нужно прослушать миниатюру события.

Вот код; в этом примере мы проверяем, что изображение имеет размер не менее 640 x 480 пикселей, прежде чем загрузить его:

 init: function() { this.on('thumbnail', function(file) { if ( file.width < 1024 || file.height < 768 ) { file.rejectDimensions(); } else { file.acceptDimensions(); } }); }, accept: function(file, done) { file.acceptDimensions = done; file.rejectDimensions = function() { done('The image must be at least 1024 by 768 pixels in size'); }; }, 

Полный пример

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

Вот HTML-код для формы:

 <form id="upload-widget" method="post" action="/upload" class="dropzone"> <div class="fallback"> <input name="file" type="file" /> </div> </form> 

Если вы реализуете защиту CSRF, вы можете добавить что-то вроде этого в свои макеты:

 <head> <!-- --> <meta name="csrf-token" content="XYZ123"> </head> 

Теперь JavaScript — обратите внимание, что мы не используем jQuery!

 Dropzone.options.uploadWidget = { paramName: 'file', maxFilesize: 2, // MB maxFiles: 1, dictDefaultMessage: 'Drag an image here to upload, or click to select one', headers: { 'x-csrf-token': document.querySelectorAll('meta[name=csrf-token]')[0].getAttributeNode('content').value, }, acceptedFiles: 'image/*', init: function() { this.on('success', function( file, resp ){ console.log( file ); console.log( resp ); }); this.on('thumbnail', function(file) { if ( file.width < 640 || file.height < 480 ) { file.rejectDimensions(); } else { file.acceptDimensions(); } }); }, accept: function(file, done) { file.acceptDimensions = done; file.rejectDimensions = function() { done('The image must be at least 640 x 480px') }; } }; 

Напоминаем, что вы найдете код для этого примера в нашем репозитории GitHub .

Надеюсь, этого достаточно, чтобы начать работу для большинства сценариев; Проверьте полную документацию, если вам нужно что-то более сложное.

Тематизация

Есть несколько способов настроить внешний вид виджета, и действительно можно полностью изменить его внешний вид.

Для примера того, насколько настраиваемым является внешний вид, вот демонстрация виджета, настроенного так, чтобы он выглядел и чувствовал себя так же, как виджет загрузки файлов jQuery с помощью Bootstrap.

Очевидно, что самый простой способ изменить внешний вид виджета — это использовать CSS. Виджет имеет класс dropzone а его составляющие элементы имеют классы с префиксом dz- ; например, dz-clickable для области clickable внутри dropzone, dz-message для заголовка, dz-preview / dz-image-preview для упаковки превью каждого из загруженных файлов и так далее. Взгляните на файл dropzone.css для справки.

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

Помимо настроек CSS, вы также можете настроить HTML, который составляет предварительный просмотр, установив previewTemplate конфигурации previewTemplate . Вот как выглядит шаблон предварительного просмотра по умолчанию:

 <div class="dz-preview dz-file-preview"> <div class="dz-image"> <img data-dz-thumbnail /> </div> <div class="dz-details"> <div class="dz-size"> <span data-dz-size></span> </div> <div class="dz-filename"> <span data-dz-name></span> </div> </div> <div class="dz-progress"> <span class="dz-upload" data-dz-uploadprogress></span> </div> <div class="dz-error-message"> <span data-dz-errormessage></span> </div> <div class="dz-success-mark"> <svg>REMOVED FOR BREVITY</svg> </div> <div class="dz-error-mark"> <svg>REMOVED FOR BREVITY</svg> </div> </div> 

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

На этом мы завершаем раздел об использовании плагина DropzoneJS. В заключение давайте посмотрим, как заставить его работать с серверным кодом.

Простой обработчик загрузки на стороне сервера с Node.js и Express

Естественно, вы можете использовать любую серверную технологию для обработки загруженных файлов. Чтобы продемонстрировать, как интегрировать ваш сервер с плагином, мы создадим очень простой пример с использованием Node.js и Express.

Для обработки самого загруженного файла мы будем использовать multer , пакет, который предоставляет некоторое промежуточное программное обеспечение Express, которое делает его действительно простым. На самом деле это просто

 var upload = multer( { dest: 'uploads/' } ); app.post( '/upload', upload.single( 'file' ), function( req, res, next ) { // Metadata about the uploaded file can now be found in req.file }); 

Прежде чем мы продолжим реализацию, наиболее очевидный вопрос, который нужно задать при работе с плагином вроде DropzoneJS, который делает за вас запросы за кулисами: «Каких ответов он ожидает?»

Обработка загрузки успешно

Если процесс загрузки успешен, единственным требованием, касающимся кода на стороне сервера, является возврат кода ответа 2xx . Содержание и формат вашего ответа полностью зависит от вас, и, вероятно, будет зависеть от того, как вы его используете; Например, вы можете вернуть объект JSON, который содержит путь к загруженному файлу или путь к автоматически сгенерированному эскизу. Для целей этого примера мы просто вернем содержимое объекта файла — то есть набор метаданных — предоставленных Multer:

 return res.status( 200 ).send( req.file ); 

Ответ будет выглядеть примерно так:

 { fieldname: 'file', originalname: 'myfile.jpg', encoding: '7bit', mimetype: 'image/jpeg', destination: 'uploads/', filename: 'fbcc2ddbb0dd11858427d7f0bb2273f5', path: 'uploads/fbcc2ddbb0dd11858427d7f0bb2273f5', size: 15458 } 

Обработка ошибок загрузки

Если ваш ответ в формате JSON — то есть тип ответа установлен на application/json — тогда плагин ошибки по умолчанию DropzoneJS ожидает, что ответ будет выглядеть следующим образом:

 { error: 'The error message' } 

Если вы не используете JSON, он просто использует тело ответа, например:

 return res.status( 422 ).send( 'The error message' ); 

Давайте продемонстрируем это, выполнив пару проверочных проверок загруженного файла. Мы просто дублируем проверки, которые мы выполняли на клиенте — помните, что проверка на стороне клиента сама по себе никогда не бывает достаточной.

Чтобы убедиться, что файл является изображением, мы просто проверим, что Mime-тип начинается с image/ . ES6 String.prototype.startsWith() идеально подходит для этого, но давайте установим для него polyfill :

 npm install string.prototype.startswith --save 

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

 if ( !req.file.mimetype.startsWith( 'image/' ) ) { return res.status( 422 ).json( { error : 'The uploaded file must be an image' } ); } 

Совет: Я использую HTTP Status Code 422, Unprocessable Entity здесь для сбоя проверки, но 400 Bad Request так же действителен; действительно, все, что находится за пределами диапазона 2хх, заставит плагин сообщать об ошибке.

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

 var dimensions = sizeOf( req.file.path ); if ( ( dimensions.width < 640 ) || ( dimensions.height < 480 ) ) { return res.status( 422 ).json( { error : 'The image must be at least 640 x 480px' } ); } 

Давайте соберем все это вместе в полном (мини) приложении:

 var express = require( 'express' ); var multer = require( 'multer' ); var upload = multer( { dest: 'uploads/' } ); var sizeOf = require( 'image-size' ); var exphbs = require( 'express-handlebars' ); require( 'string.prototype.startswith' ); var app = express(); app.use( express.static( __dirname + '/bower_components' ) ); app.engine( '.hbs', exphbs( { extname: '.hbs' } ) ); app.set('view engine', '.hbs'); app.get( '/', function( req, res, next ){ return res.render( 'index' ); }); app.post( '/upload', upload.single( 'file' ), function( req, res, next ) { if ( !req.file.mimetype.startsWith( 'image/' ) ) { return res.status( 422 ).json( { error : 'The uploaded file must be an image' } ); } var dimensions = sizeOf( req.file.path ); if ( ( dimensions.width < 640 ) || ( dimensions.height < 480 ) ) { return res.status( 422 ).json( { error : 'The image must be at least 640 x 480px' } ); } return res.status( 200 ).send( req.file ); }); app.listen( 8080, function() { console.log( 'Express server listening on port 8080' ); }); 

Совет: для краткости этот код на стороне сервера не реализует защиту CSRF; Вы можете посмотреть на пакет как CSURF для этого.

Этот код вместе со вспомогательными ресурсами, такими как представление, вы найдете в прилагаемом репозитории .

Резюме

DropzoneJS — это удобный, мощный и настраиваемый плагин JavaScript, который позволяет загружать элементы управления загрузкой файлов и выполнять загрузки AJAX. В этой статье мы рассмотрели ряд доступных опций, события и способы настройки плагина. Это гораздо больше, чем можно обоснованно описать в одной статье, поэтому посетите официальный веб-сайт, если хотите узнать больше — но, надеюсь, этого достаточно, чтобы начать работу.

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

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