5 July 2010

Делаем красивый input[type=file] с помощью jQuery

jQuery

Присказка или зачем нужен еще один плагин?


Давным-давно в тридесятом царстве в тридевятом государстве когда web был совсем не 2.0 никому и в голову не приходила мысль о стилизации форм. Сейчас же перед нами очень много решений на чистом CSS, которые кардинально меняют внешний вид элементов. К сожалению, для некоторых элементов это не работает. Особую сложность в этом плане представляет собой input[type=file].

С этим элементом, средствами CSS, мы можем разве что изменить размер шрифта. Все мы любим власть. Ты ведь хочешь полностью контролировать этот неподатливый file? Тут нам на помощь приходит волшебная связка современного интернета — JavaScript + CSS.

В нашем проекте используется jQuery, поэтому первым делом я принялся искать решение с помощью готового плагина, но быстро разочаровался. Найденные плагины либо не соответствовали требованиям нашего заказчика, либо предоставляли дополнительный функционал, который нам совершенно не нужен. Что из этого следует? Правильно – надо написать свой велосипед плагин.

На правах ТЗ


Форма в нашем проекте содержит в себе ряд чекбоксов и опциональное поле для загрузки файлов, которое, гм, немножко выделяется своим стандартным видом из корпоративного стиля. Заказчик сказал: «Хочу, чтобы загрузка файла была с помощью чекбокса!».

Мы почесали голову. В интернете подобный функционал реализовался с помощью flash или в комплекте с ajax'овой файлозагрузкой. Первый вариант отвергнут сразу — flash'em у нас просто некому заниматься, да и не любим мы его. Второй вариант был неудобен по нескольким причинам:
  • мы используем jquery form для отправки всей формы через iframe;
  • плагин не предоставлял нужных возможностей по стилизации элемента.

Developers, developers, developers, develop


Схема замены стандартного элемента выглядит следующим образом:
  • input[type=file] обертывается в <div /> с абсолютным позиционированием;
  • в случае отсутствия в параметрах существующего «заменителя», он создается на бывшем месте нашего элемента;
  • «обертка» с input[type=file] располагается точно над нашим заменителем.

«Почему все так сложно?!» — спросите вы. К сожалению, эмуляция click на input[type=file] работает не совсем хорошо или совсем не хорошо, поэтому необходима такая уличная магия.

Теперь собственно как это все работает:
  1. function replace() {
  2.   $replacement
  3.     .addClass(config.replacementClass + '-' + currentId) //генерируем уникальный CSS class name
  4.     .insertBefore($input) //вставляем заменитель после нашего input'a
  5.     .bind('mouseenter', function () {
  6.       toggleVisibility(true);
  7.       toggleHoverClass(true);
  8.     })
  9.     .bind('init', function () {
  10.       createWrapper(); //создаем обертку
  11.       createFileName();//создаем поле для вывода файлов
  12.     })
  13.     .trigger('init');
  14. }
* This source code was highlighted with Source Code Highlighter.


Рассмотрим подробнее создание обертки:

  1. function createWrapper() {
  2.   var cord = getElementCoordinates($replacement); //необходимо чтобы обертка занимала точно такие же размеры, как и заменитель
  3.   var wrap = $('<div />', {
  4.     css : {
  5.       position : "absolute",
  6.       visibility : "hidden", //обертка скрыта по умолчанию
  7.       overflow : "hidden",
  8.       zIndex : 10000000, //позиционируем ее "выше" всех других элементов на странице
  9.       opacity : 0, //мы не должны видеть стандартную кнопку для загрузки
  10.       left : cord.left,
  11.       top : cord.top,
  12.       width : $replacement.get(0).offsetWidth,
  13.       height : $replacement.get(0).offsetHeight,
  14.       margin : 0,
  15.       padding : 0,
  16.       direction : "ltr"
  17.     }
  18.   })
  19.   .addClass(config.wrapperClass)
  20.   .bind('mouseout', function () {
  21.     toggleVisibility(false);
  22.     toggleHoverClass(false);
  23.   });
  24.  
  25.   $input.wrap(wrap).css(config.inputCss);
  26. }
* This source code was highlighted with Source Code Highlighter.


Кроме этого в последней строчке мы применяем к стандартному элементу следующие CSS-стили:

  1. inputCss: {
  2.   fontSize : '600px', //делаем кнопку "Browse" максимальной большой
  3.   position : "absolute",
  4.   right : 0, //выравниваем её по правой границе обертки
  5.   margin : 0,
  6.   padding : 0
  7. },
* This source code was highlighted with Source Code Highlighter.


Это необходимо для того, чтобы нажимая на нашу обертку мы обязательно попадали по кнопке input[type=file] и вызывали стандартный диалог для загрузки файла.

Создание поля для вывода имени файла очень простое:

  1. function createFileName() {
  2.   try {
  3.     if (! jQuery.contains(document.body, $filename.get(0))) {
  4.       throw ("not found");
  5.     }
  6.   } catch (e) {
  7.     if (! $filename.length) {
  8.       throw ("filename is empty");
  9.     }
  10.     $replacement.after($filename);
  11.   }
  12.   $filename.addClass(config.filenameClass + '-' + currentId);
  13. }
* This source code was highlighted with Source Code Highlighter.


В случае, если элемент для вывода имени не присутствует на странице, то мы добавляем его сразу после нашей обертки.

Базовый функционал готов, но нам необходимо предусмотреть возможность удаления выбранного файла. В этом кроется очередная сложность — value у input[type=file] доступно только для чтения. Приступаем к очередному фокусу — созданию нового input'a на лету, копируя при этом все его характеристики:

  1. function onClear() {
  2.   var el  = $input.get(0);
  3.   var attrs = {};
  4.   //копируем все атрибуты старого input'a за исключением значения
  5.   for (var i = 0; i < el.attributes.length; i += 1) {
  6.     var attrib = el.attributes[i];
  7.     if (attrib.specified === true && attrib.name !== 'value') {
  8.       attrs[attrib.name] = attrib.value;
  9.     }
  10.   }
  11.   attrs.value = "";
  12.   //создаем новый input и заменяем им старый
  13.   $input = $('<input />', attrs);
  14.   $(this).replaceWith($input);
  15.  
  16.   //привязываем к нему события
  17.   $input.css(config.inputCss).bind('change', onChange).bind('clear', onClear).trigger('change');
  18.   toggleActiveClass(false);
  19. }
* This source code was highlighted with Source Code Highlighter.


Help me


Использовать плагин очень просто — надо подключить последний jQuery, сам скрипт плагина и прописать следующие строки:
  1.  <script type="text/javascript">
  2.   $(function () {
  3.     $("#fileupload-1").customInputFile({
  4.       filename: "#filename-1"
  5.     });
  6.     $("#fileupload-2").customInputFile();
  7.     $('#clear').bind('click', function () {
  8.      $("#fileupload-1, #fileupload-2").trigger("clear");
  9.     });
  10.   });
  11.  </script>
* This source code was highlighted with Source Code Highlighter.


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

Сброс значений инициализируется вызовом события «clear» на заменяемых input'ах. В параметрах возможно также указание CSS классов для следующих ситуаций:
  • файл не выбран;
  • файл выбран;
  • :hover, когда файл не выбран;
  • :hover, когда файл выбран;

Они достигаются при помощи комбинации трех классов:
  1.       replacementClass    : "customInputFile",
  2.       replacementClassHover : "customInputFileHover",
  3.       replacementClassActive : "customInputFileActive",
* This source code was highlighted with Source Code Highlighter.

Демонстрация и раздаточные материалы


Результаты работы плагина можно посмотреть на этих двух картинках:

work

demo

Исходные коды проекта размещены на bitbucket, а живую демонстрацию можно посмотреть здесь
Tags: jquery jquery plugins javascript input file велосипед хитрый заказчик
Hubs: jQuery
+33
27.2k 123
Comments 16
Ads