От автора: Всем известно, что если нужно внести изменения в соответствии с требованиями заказчика, то поля выбора файла очень ограничены, и тогда как существует множество сложных плагинов с дюжинами альтернативных вариантов, позволяющих вам подогнать их, иногда трудно заставить эти поля работать. Этот учебник проведет вас по процессу создания плагина jQuery с поддержкой множества файлов и простой альтернативой для старых браузеров, гм…, IE9-8, заменяющего стандартный уродливый input.
Подготовка проекта
Сначала давайте создадим папку customFile и 3 файла: jquery.customFile.js, jquery.customFile.css и customFile.html. Возьмите этот шаблон HTML и скопируйте/вставьте его в свой проект.
Теперь, когда у нас есть все, что нужно, давайте откроем свой файл HTML и добавим контейнер и input с типом file с его меткой label:
1 2 3 4 |
<div class="customfile-container"> <label>File: </label> <input type="file" id="file" name="myfiles[]" multiple /> </div> |
Также убедитесь, что задали ему id и название массива, такое как myfiles[] с тем, чтобы сервер мог отыскать все имена файлов с альтернативой для IE, о чем мы поговорим позже.
Далее откройте jquery.customFile.js и установите основной плагин-заготовку jQuery:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
;(function( $ ) { $.fn.customFile = function() { return this.each(function() { var $file = $(this).addClass('customfile'); // исходный файловый ввод // здесь код }); }; }( jQuery )); |
Наконец, вызовите в своей разметке плагин:
1 |
<script>$('input[type=file]').customFile()</script> |
Как это работает
Для построения модифицированной пользовательской замены нам понадобится простая структура разметки:
Щелчок по кнопке «open» запустит событие щелчка «click» в исходном файловом input-е. После выбора файла исходный input запускает событие «change», где мы установим значение inputa путем получения доступа к файловому массиву, если файловый API поддерживается, или иначе получив доступ к исходному значению.
Создание плагина
Сначала нужно протестировать браузер на поддержку multiple. Проще всего – создать input и проверить, имеется ли у него в наличии свойство multiple, иначе браузер не станет поддерживать множество файлов. Нам также нужно проверить, не является ли браузер IE, чтобы позже кое-что в нем поправить. Этот код можно вынести наружу плагина, так как он не зависит от самого элемента.
1 2 3 |
// Поддерживает ли браузер множество файлов HTML5? var multipleSupport = typeof $('<input/>')[0].multiple !== 'undefined', isIE = /msie/i.test( navigator.userAgent ); // просто, но не супербезопасно... |
Теперь давайте создадим элементы, необходимые для замены. У IE имеются серьезные меры безопасности, предотвращающие извлечение имени файла, если input запускается извне, поэтому мы применим вместо кнопки button метку label. Запустив событие по метке, можно проработать этот вопрос.
1 2 3 4 |
var $wrap = $('<div class="customfile-wrap">'), $input = $('<input type="text" class="customfile-filename" />'), $button = $('<button type="button" class="customfile-upload">Open</button>'); $label = $('<label class="customfile-upload" for="'+ $file[0].id +'">Open</label>'); |
Атрибут type=»button» нужен для устойчивости, для предотвращения отправки формы некоторыми браузерами. Далее давайте отделаемся от исходного input-а. Вместо того, чтобы скрыть его, давайте удалим из поля зрения, сдвинув влево, таким образом, мы все же сможем использовать его даже когда он невидим; это подходит для запуска событий, что может оказаться проблематичным, если input просто скрыт.
1 2 3 4 |
$file.css({ position: 'absolute', left: '-9999px' }); |
Наконец давайте прикрепим к DOM свои новые элементы:
1 |
$wrap.insertAfter( $file ).append( $file, $input, ( isIE ? $label : $button ) ); |
На этом этапе у вас должно уже получиться нечто, выглядящее примерно так в нормальном браузере; об IE мы позаботимся позже.
Прикрепление событий
Самое первое, что следует сделать – предотвратить попадание в фокус исходной формы, а также свежесозданной кнопки. Оказаться в фокусе сможет только текстовый input.
1 2 |
$file.attr('tabIndex', -1); $button.attr('tabIndex', -1); |
Давайте запустим на кнопке событие щелчка click для открытия диалога. В IE, поскольку там нет настоящей кнопки, метка должна уже запустить диалог без дополнительного труда.
1 2 3 |
$button.click(function () { $file.focus().click(); // Открыть диалог }); |
Событие focus в некоторых браузерах нужно запускать, так что событие щелчка сработает должным образом. Если на этом этапе попытаться щелкнуть в браузере «open», то должен открыться файловый диалог. Теперь можно применить событие change, запускаемое после выбора файла для заполнения значения текстового input-a выбранным файлом (файлами).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
$file.change(function() { var files = [], fileArr, filename; // Если есть поддержка multiple, то выделите // все имена файлов из файлового массива if ( multipleSupport ) { fileArr = $file[0].files; for ( var i = 0, len = fileArr.length; i < len; i++ ) { files.push( fileArr[i].name ); } filename = files.join(', '); // Если поддержка отсутствует, то возьмите значение // и удалите путь, чтобы показать только имя файла } else { filename = $file.val().split('\\').pop(); } $input.val( filename ) // Установите значение .attr('title', filename) // Покажите имя файла в всплывающей подсказке заголовка .focus(); // Regain focus }); |
Улучшение юзабилити
Пока все работает хорошо, но можно сделать кое-что для улучшения простоты применения и общего поведения своей новой замены.
Запускайте событие blur в исходном input-e, когда замена теряет фокус.
Открывайте диалог при нажатии пользователем «enter» в текстовом input-e кроме IE (из-за ограничений безопасности это не сработает).
Удаляйте файлы с помощью «backspace» или «delete», или же пользователю придется открыть диалог и нажать для очистки ввода «cancel».
Другими словами:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
$input.on({ blur: function() { $file.trigger('blur'); }, keydown: function( e ) { if ( e.which === 13 ) { // Enter if ( !isIE ) { $file.trigger('click'); } } else if ( e.which === 8 || e.which === 46 ) { // Backspace & Del // В некоторых браузерах значение не модифицируется // с помощью приема, которым мы удаляем старый input и добавляем // чистый клон со всеми прикрепленными исходными событиями $file.replaceWith( $file = $file.clone( true ) ); $file.trigger('change'); $input.val(''); } else if ( e.which === 9 ){ // TAB return; } else { // Все остальные ключи return false; } } }); |
Альтернативы для старых браузеров
Самая легкая альтернатива получения возможности ввода большого количества файлов – создание множественных input-ов. Когда файл выбран, мы создаем новый input и, когда input очищен, удаляем его.
Этот код следует за плагином, так как предназначен для применения ко всем пользовательским файловым input-ам. Тут нам нужно использовать on, чтобы делегировать событие последующим, пока не существующим input-ам.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
if ( !multipleSupport ) { $( document ).on('change', 'input.customfile', function() { var $this = $(this), // Создайте уникальный ID, чтобы можно было // прикрепить к input-у метку uniqId = 'customfile_'+ (new Date()).getTime(); $wrap = $this.parent(), // Отфильтруйте пустой input $empty = $wrap.siblings().find('.customfile-filename') .filter(function(){ return !this.value }), $file = $('<input type="file" id="'+ uniqId +'" name="'+ $this.attr('name') +'"/>'); // таймаут 1мс, поэтому запускается после всех прочих событий, // модифицирующих запущенное событие setTimeout(function() { // Добавьте новый input if ( $this.val() ) { //Отметьте пустое поле, чтобы предотвратить // создание новых input-ов при изменении файлов if ( !$empty.length ) { $wrap.after( $file ); $file.customFile(); } // Удалите и реорганизуйте input-ы } else { $empty.parent().remove(); // Сдвиньте input так, чтобы он всегда оставался последним в списке $wrap.appendTo( $wrap.parent() ); $wrap.find('input').focus(); } }, 1); }); } |
Модифицирование внешнего вида
На этом этапе все уже должно работать, поэтому давайте «приправим блюдо» стилями:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
/* Рассчитать ширину легче * с помощью разметки border-box */ .customfile-container * { box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; font: normal 14px Arial, sans-serif; /* Общий шрифт для точного применения em’ов */ } .customfile-container { width: 300px; background: #FFF2B8; padding: 1em; } .customfile-container label:first-child { width: 100px; display: block; margin-bottom: .5em; font: bold 18px Arial, sans-serif; color: #333; } .customfile-wrap { position: relative; padding: 0; margin-bottom: .5em; } .customfile-filename, .customfile-upload { margin: 0; padding: 0; } .customfile-filename { width: 230px; padding: .4em .5em; border: 1px solid #A8A49D; border-radius: 2px 0 0 2px; box-shadow: inset 0 1px 2px rgba(0,0,0,.2); } .customfile-filename:focus { outline: none; } .customfile-upload { display: inline-block; width: 70px; padding: .4em 1em; border: 1px solid #A8A49D; background: #ddd; border-radius: 0 2px 2px 0; margin-left: -1px; /* выровнять по вводу */ cursor: pointer; background: #fcfff4; background: -moz-linear-gradient(top, #fcfff4 0%, #e9e9ce 100%); background: -webkit-linear-gradient(top, #fcfff4 0%, #e9e9ce 100%); background: -o-linear-gradient(top, #fcfff4 0%, #e9e9ce 100%); background: -ms-linear-gradient(top, #fcfff4 0%, #e9e9ce 100%); background: linear-gradient(to bottom, #fcfff4 0%, #e9e9ce 100%); } .customfile-upload:hover { background: #fafafa; box-shadow: 0 0 2px rgba(0,0,0,.2); } .customfile-upload::-moz-focus-inner { /* Исправьте отступ firefox */ padding: 0; border: 0; } |
Продолжайте работу и модифицируйте CSS для создания собственного внешнего вида.
Поиск файлов на сервере
Первым делом оберните свой input в форму и добавьте кнопку отправки submit:
1 2 3 4 5 6 7 |
<form action="test.php" method="post" enctype="multipart/form-data"> <div class="customfile-container"> <label>File: </label> <input type="file" id="file" name="myfiles[]" multiple /> </div> <button type="submit">Submit</button> </form> |
Затем мы можем получить все имена файлов и напечатать их в test.php:
1 2 3 |
<?php $files = $_POST['myfiles']; // Массив, содержащий все файлы echo implode( $files, '<br/>' ); |
Так как мы применяем имя массива myfiles[], сервер отыщет все файлы, даже если используется альтернативный вариант. Узнать больше можно в руководстве PHP о загрузке множества файлов (Uploading Multiple Files).
Заключение
Файловые inpu-ы можно сравнительно легко и без особых усилий приспосабливать под требования заказчика. Альтернативный вариант, естественно, не идеальный выход, но он работает и его легко поддерживать без сотен строк кода или других техник типа Flash, Silverlight и т.д…
Плагин тестировался на IE9-8 и всех современных браузерах. Возьмите полный код или поиграйте с демоверсией. Если есть предложения, пожалуйста, оставьте ниже комментарий.
Автор: Cedric Ruiz
Источник: //www.onextrapixel.com/
Редакция: Команда webformyself.