Заменяем стандартный элемент input file

    Недавно, занимаясь кастомизацией (да простят меня руссоведы) я бы даже назвал это веб-моддингом (ещё раз извините, уважаемые руссоведы) стандартных элементов формы, а в частности элемента file, я наткнулся на большую неприятность: он оказался не профпригоден для настройки. Суть вот в чём: от элемента file нам особенно нужно его диалоговое окно "Upload files", добыть который программными средствами в браузерах Mozilla и Opera невозможно, т.е. команда document.getElementById('SaveForm').click(); ничего не вернёт. Разработчики уверяют что это небезопасно, что спорно. А вот IE меня порадовал, он беспрепятственно отправляет нажатие мыши в элемент file, скорее всего по недосмотру разработчиков, оставивших «опаснейшую дыру» в Вашей безопасности.

    Как я только не изворачивался ничего не получалось. Прогуглив три часа, наткнулся на первое свиду идеальное решение: поместить file в div, сделать div прозрачным (opacity: 0%), обращаю ваше внимание именно на то что сделать его нужно прозрачным, но не невидимым (visibility: hidden), условно говоря opacity: 0% прячет элемент от глаз, а visibility:hidden ещё и от мыши. Но если поиграть с размерами шрифтов, метод начинает терять свою привлекательность, появляется возможность промахнуться мимо невидимой кнопки.
    Спустя час, я наткнулся на ещё один пример yamzbrowser (позволяет осуществлять загрузку файлов на сервер из swf-объекта вставленного в html-страницу). Он повторяет первый метод, но! с одной лишь разницей: он подставляет div под курсор мыши при перемещении. Кстати, обнаружить это мне удалось именно благодаря Опере (обычно я пользуюсь Лисой, и это большая случайность что я открыл пример именно Оперой), так как автор забыл установить прозрачность ещё и для неё.

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

    Допущения


    1. Размер элемента подменяющего file могут значительно отличаться от размеров самого file'а
    2. Элемент file явлеятся частью формы
    3. Только параноик отключает JavaScript в браузере
    4. Посетитель относится к тем скромным 99% пользователей использующих Firefox, Opera или IE
    5. Решение должно работать по одинаковому алгоритму во всех браузерах, т.е. вариант использования разных средств для IE — document.getElementById('SaveForm').click();, а для FF и Opera — подстановка, неприемлем
    6. Решение может дублироваться на странице

    Алгоритм


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

    Рецепт


    HTML


    <form>
    <div id="divFile1" class="file" onmousemove="divFileMove(this.id, 'buttonFile1',event);">
    <input type="file" name="UploadFile" id="file1" size="1" onchange="result(this.id, 'divResult');" tabindex="1" />
    </div>
    <div id="divResult"> </div>
    <image src="./images/fileUploadButton.jpg" id="buttonFile1" onmouseover="divFileShow('divFile1', event);"/>
    </form>

    CSS



    div.file{
      position: absolute;
      top: -1000px;
      left: -1000px;
      -moz-opacity: 0;
      filter: alpha(opacity=0);
      opacity: 0%;
      visibility: hidden;
      }


    div.file — используется именно класс .file а не id, ввиду допущения номер 6.
    top, left: -1000px — прячет слой до того как до него доберётся javascript, так на всякий случай.
    -moz-opacity, filter: alpha, opacity — задаёт прозрачность для FF, IE и Opera соответственно.
    visibility: hidden — скрывает от мыши.

    JavaScript


    Функция divFileShow(divId,e), где divId — id слоя с элементом file, e — event. Функции mouseX и mouseY высчитывают положение мыши относительно левого верхнего угла для всех браузеров. Делает слой видимым для мыши и подставляет под курсор таким образом чтобы курсор оказался посередине слоя, так мы точно не промахнёмся.

    function divFileShow(divId,e){
      var div = document.getElementById(divId);
      div.style.left = (mouseX(e) - Math.round(div.offsetWidth/2)) + "px";
      div.style.top = (mouseY(e) - Math.round(div.offsetHeight/2)) + "px";
      div.style.visibility = "visible";
      }

    Функция divFileMove(divId, butId, e), где divId — id слоя, butId — id подменяющей кнопки (необходим для получения габаритов кнопки), e — event. Она заставляет слой двигаться за мышью, при досижении границы объекта butId.
    function divFileMove(divId, butId, e){
      var div = document.getElementById(divId);
      var button = document.getElementById(butId);
      var move = true;
      var divX = parseInt(div.style.left) + Math.round(div.offsetWidth/2);
      var divY = parseInt(div.style.top) + Math.round(div.offsetHeight/2);
      if (divX < button.offsetLeft || divX > (button.offsetLeft + button.offsetWidth)) {
        move = false;
        }
      if (divY < button.offsetTop || divY > (button.offsetTop + button.offsetHeight)){
        move = false;
        }
      if (move){
        div.style.left = (mouseX(e) - div.offsetWidth/2) + "px";
        div.style.top = (mouseY(e) - div.offsetHeight/2) + "px";
      } else {
        div.style.visibility = "hidden";
        }
      }

    Функция result(fileId, divResultId), где fileId — id элемента файл, divResultId — id слоя куда будет выведено имя выбранного файла. Функция передаёт в innerHTML слоя только имя файла(например, «all my passwords.txt»), а в атрибут title помещает полный путь, по-моему, это удобнее.

    function result(fileId, divResultId){
      var divResult = document.getElementById(divResultId);
      var formFile = document.getElementById(fileId);
      divResult.innerHTML = formFile.value.replace(/^([^\\\/]*(\\|\/))*/,"");
      divResult.setAttribute("title",formFile.value);
      }

    И на последок две функции (mouseX(e), mouseY(e), где е — event) определяющие местоположение курсора на странице для IE и остальных браузеров, они отличаются только двумя буквами поэтому приведу одну из них.
    function mouseX (e){
      if (e.pageX) return e.pageX;
      if (e.clientX) return e.clientX + document.body.scrollLeft;
      }

    Примечание


    Так как слой с элементом file перекрывает кнопку, ей не передаётся событие :hover, поэтому если вам необходимо сымитировать наведение мыши Вам придётся добавить соответствующй функционал самостоятельно либо путём изменения класса кнопки, либо изменения изображения.
    Рабочий пример, если кто-то хочет посодействовать правому делу разместите пример у себя для онлайн презентации, ссылку и благодарности в эту статью гарантирую.
    Тестировалось в браузерах Firefox 2, Opera 9, IE 6.
    Спасибо за внимание.

    P.S. О багах сообщайте заранее не отходя от кассы ;)

    Similar posts

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

    More

    Comments 32

      0
      точно также я когдато делал "input file" из Flash :)
        0
        Решение без JS

        Выбрать файл
        0
        Иногда диалоговое окошко не открывается. Так и не удалось его вызвать, кликая в центре буквы I. IE6
          0
          Уже фиксю ;)
            0
            Баг пофиксен. Детали позднее...
            0
            Очень уж мерцает кнопка "Обзор..."
              0
              Мерцает? А вы в каком браузере проверяли?
                0
                Opera 9.22
                  0
                  спасибо, пофиксил, работает...
              0
              В ФФ2 за мышкой постоянно гоняет поле Файл :)



              В ИЕ6 верстка косячит (ну это уже мелочь правда).
              0
              Возможно я не в теме :)
              Но как я понял из первой половины статьи (вторую я по своей велико-человеческой лени пропустил) Проблема в том чтобы создавать кнопку открытия диалогового окна подгрузки файла произвольного размера.
              Если так, то я как-то повстречал тривиальное, но хрен допрешь решение :)
              file инпут как и в начале метода автора помещается в див. Див имеет overflow: hidden и такие размеры которые нам надо (т.е. произвольный). Это всё понятно, но как сделать чтобы кнопка занимала нужное пространство - а здесь есть хак с font-size в инпуте... увеличенный шрифт разрастает кнопку, а из-за overflow: hidden див не расширяется :) итог - весь див есть кнопка, ну конечно надо поставить opacity в 0, чтобы артефакты скрыть и ессно, file отодвинуть так чтобы все кроме кнопки ушло запределы дива...
              скорее хак чем солушн :) но в FF и IE работает без проблем :)
                0
                По-моему отличный солюшен. Никакого JS, и любой размер изображения.
                Разве использование overflow: hidden и opacity: 0 - это хак?
                  0
                  Все конечно хорошо, но в FF3 «расширяется» не сама кнопка, а поле ввода и при этом курсор приобретает форму «редактирования»
                  0
                  ну почему, объясните мне, когда я выкладывал свой тулкит для замены селекта (см. в моём профиле), то на меня сразу наехало куча народу по поводу того, что он криво поддерживался Safari и не работал в Lynx? а тут, нате пожалуйста - 99% в лисе, опере и ие, скрипты вообще отключают психи.. я, конечно, согласен с этим, но вы только почитайте комментарии в моем топике..
                  обидно блин. :(
                    +1
                    Искренне сочувствую. Именно твой тулкит (ничего что я на ты?) подтокнул меня к написанию этого топика да и вообще к разработке идеи. А успех твоей заметки выше, а это уже хорошо.
                    Кстати, у меня родилась идея (опять же с твоей работы) разработки тулкита для обработки форм (всё-таки jQuery, Prototype, Dojo и иже с ними справляются с этим каждый раз по разному, хотя jQuery лучше всех, вспомнить бы где видел тесты, 10 минут и вспомнил), основан он на расширяемости xml и динамичности jscript т.е. создаётся поле например проверяющее себя по regexp и данный атрибут включается в input text, а jscript находит его и привинчивает необходимые функции, таким образом: <input onChange="alert('hello');" type="text" name="email" regexp="/^[a-z0-9] и т.д." ... />
                    Если интересно, могу развить идею в личку.
                      0
                      Это все будет в XForms
                    0
                    А давайте всё же делать сайты, которые просты, юзабельны и быстры?
                    Давайте всё же прислушиваться к w3c и не игнорировать слепых людей, людей без JS или на links?

                    Давайте не делать «дизайн» засчёт пользователей?
                      0
                      Я сам за такой дизайн но не могу же я заставить Firefox и Opera пересмотреть свою политику и переработать существующие компоненты хотя бы до того чтобы их можно было гибко настраивать.
                      Способ который я вижу на данный момент - это разработать и внедрить такие компоненты, которые захотят включить в свои продукты эти компании. Да и вообще если почитать Паркинсона, то становится ясно почему w3c теряет свою эффективность...
                        0
                        Ещё и ещё раз повторю - существуют не только четыре основных браузера. И нельзя игнорировать остальных. w3c слабеет, поскольку все хотят такие же эффектные, нажимающиеся, классные сайты. А w3c повторяет по тому же кругу - accessability, usability! Простите, но я с w3c.
                          0
                          а для чего по вашему нужен комбобокс?
                          не для удобства пользователя?
                            0
                            Вы о чём?
                              0
                              сорри, я просто привет свой пример - мой тулкит эмулирует комбобоксы, текстбоксы с подстановкой, мултиопционные селекты и т.п. (http://alx.vingrad.ru/fwc)
                              все это для упрощения работы с формами пользовтеля!! и поможет он гораздо большим людям, чем помешает тем, у кого выключен JavaScript..
                              я сейчас о usability.
                                0
                                Задумка хороша, но всё же было бы замечательно, если бы они имели graceful degradation.

                                И не думайте, что свои контролсы только помогают. Часто они ставят пользователя в небольшой тупичок, поскольку они для него необычны.
                                  0
                                  я размещал заметку на Хабрахабре - http://www.habrahabr.ru/blog/webdev/2388…
                                  для достижения работы в браузерах, где выключен JavaScript, достаточно использовать тег noscript. делать это автоматом совершенно неудобно, тем более, что компонент smartselect - далеко не всегда select. это может быть и input и просто выпадающее меню. проще всего самостоятельно описать альтернативу в Noscript.
                                  сорри за оффтоп.

                                  а по поводу Input:filе - у меня есть идея, как сделать это чуть лучше, я реализую это в ближайшее время как бонус к smartselect`у.
                            0
                            Нельзя делать идеальные решения, если нужен коммерческий успех нужно ориентироваться на большинство, на платёжеспособное большинство... Это правила капитализма, извините.
                          0
                          зачем? сколько их таких, людей на links?
                            0
                            Понимаете, тут дело немного в другом.

                            Пока вы делаете сайт, который хорошо читается в links, вы уверены, что он будет хорошо читаться слепыми, на мобильниках и на любых любых устройствах. Вы оставляете универсальность и открытость интернета. А чем больше у нас распростроняется vendor lock, тем хуже интернету. Linux, кстати, это ещё не всё помимо окошек, так что не надо говорить, что «Firefox работает и под линь».
                          0
                          В Opera "opacity: 0%;" не работает, надо "opacity: 0;"
                          opacity - это число от 0 до 1, так что зачем % не понятно.
                            0
                            Спсибо. Недоглядел
                            0
                            Просили сообщить о багах, вот один:

                              0
                              Вроде все ОК.

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