Делаем красивый input[type=file] с помощью 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, а живую демонстрацию можно посмотреть здесь

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 16

      +1
      К сожалению, эмуляция click на input[type=file] работает не совсем хорошо или совсем не хорошо, поэтому необходима такая уличная магия.
      Правильнее сказать, совсем не работает (особенно коварно в IE — работает на первые 50%).
        0
        Я уже устал ругаться на поведение IE, да и обещал девушке использовать поменьше нецензурных слов — поэтому выразился политкорректно :)
          +13
          Девушка-тиран следит за Вами на Хабре?
            +13
            Я просто не хочу ее обманывать
            –2
            >обещал девушке

            Казалось бы, при чем здесь эта картинка…

          +1
          Opera вне закона?
            0
            Проверил работу в 10.60 — в целом в работает. Можете описать проблему более подробно и с указанием версии?
              +1
              У меня тоже 10.60 (Windows)
              После выбора файла в «Here should be our filenames:» не отображается ничего
                0
                Хорошо, попробую подебажить эту проблему
            0
            в Opera 10.60 видны только две кнопки Upload и Clear внизу. Хотя клик по пустому месту вверху вызывает диалог открытия файла.
              +1
              А если _под_ filefield подложить простой, стилизованный input, filefield'у сделать opacity:0, то при клике на этом месте будет открываться диалоговое окно. после выбора файла, js берет значение из filefield'a и кладет в наш простой input, который виден.
              работает. проверено электроникой ;)

              ps. ну, или вот так можно bixi.su/request
              ps2. за страшную кнопку знаю, прошу не пинать ;)
                0
                Это один из вариантов, но он не работает для нашего случая — поле для загрузки файла должно быть минимальным по ширине и высоте.
                0
                хром 5.0.375.99, osx — не работает. Во-первых, ховер у кнопок отрабатывает странно. Определенными манипуляциями можно обе кнопки насовсем заховерить. Во-вторых, после залития первого файла, обе кнопки перестают отзываться.
                  0
                  У меня отзываюься только если нажимать на кнопку почти в близи к верхней границе самой кнопки.
                  0
                  Вот еще один велосипед vremenno.net/js/javascript-snippets-plus-new-file-input/
                    0

                    Only users with full accounts can post comments. Log in, please.