Как создать нестандартный input с типом file с помощью jQuery, CSS3 и PHP

Нестандартный input типа file

От автора: Всем известно, что если нужно внести изменения в соответствии с требованиями заказчика, то поля выбора файла очень ограничены, и тогда как существует множество сложных плагинов с дюжинами альтернативных вариантов, позволяющих вам подогнать их, иногда трудно заставить эти поля работать. Этот учебник проведет вас по процессу создания плагина jQuery с поддержкой множества файлов и простой альтернативой для старых браузеров, гм…, IE9-8, заменяющего стандартный уродливый input.

демо

Подготовка проекта

Сначала давайте создадим папку customFile и 3 файла: jquery.customFile.js, jquery.customFile.css и customFile.html. Возьмите этот шаблон HTML и скопируйте/вставьте его в свой проект.

Теперь, когда у нас есть все, что нужно, давайте откроем свой файл HTML и добавим контейнер и input с типом file с его меткой label:

<div class="customfile-container">
  <label>File: </label>
  <input type="file" id="file" name="myfiles[]" multiple />
</div>

Также убедитесь, что задали ему id и название массива, такое как myfiles[] с тем, чтобы сервер мог отыскать все имена файлов с альтернативой для IE, о чем мы поговорим позже.

Далее откройте jquery.customFile.js и установите основной плагин-заготовку jQuery:

;(function( $ ) {
 
  $.fn.customFile = function() {
 
    return this.each(function() {
 
      var $file = $(this).addClass('customfile'); // исходный файловый ввод
 
      // здесь код
 
    });
 
  };
 
}( jQuery ));

Наконец, вызовите в своей разметке плагин:

<script>$('input[type=file]').customFile()</script>

Как это работает

Для построения модифицированной пользовательской замены нам понадобится простая структура разметки:

Щелчок по кнопке «open» запустит событие щелчка «click» в исходном файловом input-е. После выбора файла исходный input запускает событие «change», где мы установим значение inputa путем получения доступа к файловому массиву, если файловый API поддерживается, или иначе получив доступ к исходному значению.

Создание плагина

Сначала нужно протестировать браузер на поддержку multiple. Проще всего – создать input и проверить, имеется ли у него в наличии свойство multiple, иначе браузер не станет поддерживать множество файлов. Нам также нужно проверить, не является ли браузер IE, чтобы позже кое-что в нем поправить. Этот код можно вынести наружу плагина, так как он не зависит от самого элемента.

// Поддерживает ли браузер множество файлов HTML5?
var multipleSupport = typeof $('<input/>')[0].multiple !== 'undefined',
    isIE = /msie/i.test( navigator.userAgent ); // просто, но не супербезопасно...

Теперь давайте создадим элементы, необходимые для замены. У IE имеются серьезные меры безопасности, предотвращающие извлечение имени файла, если input запускается извне, поэтому мы применим вместо кнопки button метку label. Запустив событие по метке, можно проработать этот вопрос.

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 просто скрыт.

$file.css({
  position: 'absolute',
  left: '-9999px'
});

Наконец давайте прикрепим к DOM свои новые элементы:

$wrap.insertAfter( $file ).append( $file, $input, ( isIE ? $label : $button ) );

На этом этапе у вас должно уже получиться нечто, выглядящее примерно так в нормальном браузере; об IE мы позаботимся позже.

Прикрепление событий

Самое первое, что следует сделать – предотвратить попадание в фокус исходной формы, а также свежесозданной кнопки. Оказаться в фокусе сможет только текстовый input.

$file.attr('tabIndex', -1);
$button.attr('tabIndex', -1);

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

$button.click(function () {
  $file.focus().click(); // Открыть диалог
});

Событие focus в некоторых браузерах нужно запускать, так что событие щелчка сработает должным образом. Если на этом этапе попытаться щелкнуть в браузере «open», то должен открыться файловый диалог. Теперь можно применить событие change, запускаемое после выбора файла для заполнения значения текстового input-a выбранным файлом (файлами).

$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.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».

Другими словами:

$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-ам.

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);
 
  });
}

Модифицирование внешнего вида

На этом этапе все уже должно работать, поэтому давайте «приправим блюдо» стилями:

/* Рассчитать ширину легче 
 * с помощью разметки 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:

<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:

<?php
$files = $_POST['myfiles']; // Массив, содержащий все файлы
echo implode( $files, '<br/>' );

Так как мы применяем имя массива myfiles[], сервер отыщет все файлы, даже если используется альтернативный вариант. Узнать больше можно в руководстве PHP о загрузке множества файлов (Uploading Multiple Files).

Заключение

Файловые inpu-ы можно сравнительно легко и без особых усилий приспосабливать под требования заказчика. Альтернативный вариант, естественно, не идеальный выход, но он работает и его легко поддерживать без сотен строк кода или других техник типа Flash, Silverlight и т.д…

Плагин тестировался на IE9-8 и всех современных браузерах. Возьмите полный код или поиграйте с демоверсией. Если есть предложения, пожалуйста, оставьте ниже комментарий.

Автор: Cedric Ruiz

Источник: http://www.onextrapixel.com/

Редакция: Команда webformyself.

JavaScript&jQuery с нуля до профи

Пройдите пошаговый видеокурс по JavaScript&jQuery

Научиться

Метки:

Комментарии Вконтакте:

Комментарии Facebook:

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Я не робот.

Spam Protection by WP-SpamFree