Привет, Хабр!
Как-то, год назад я писал о своей небольшой библиотеке, которая облегчает разработку форм на сайтах. Недавно я выпустил 3-ю версию, которая, по-сути, была переписана с нуля, чтобы стать правильней и удобней. Но в своей статье я не буду повторять README и ДЕМО, а лучше покажу на практике, каким образом она помогает писать меньше кода.
Но прежде, хотелось бы дать немного информации о новой версии. В ней, я избавился от зависимости malsup jquery.form для отправки файлов, и один большой репозитарий я разделил на несколько мелких самостоятельных репозитариев, сохранив, при этом, общую сборку "всё-в-одном":
- поддержка дополнительных событий форм (form-extra-events);
- полифил HTML5 form-атрибутов (form-association-polyfill);
- поддержка отправки файлов через iframe для старых браузеров (jquery-iframe-ajax);
- и, собственно, сама библиотека (paulzi-form).
Дополнительные события форм
Во второй версии библиотеки я столкнулся с проблемой необходимости соблюдения определённого порядка подключения скриптов. Например, раньше нельзя было подключать данный скрипт перед стандартным скриптом валидации Yii2, дело в том, что на событие submit навешиваются все скрипты, которые так или иначе должны обработать отправку, в частности произвести валидацию, и нет никакой гарантии что кто-то это событие не прервёт вызовом
preventDefault()
. Поэтому, в случае неверного порядка подключения, сначала происходила блокировка формы, а затем валидация, и, если были ошибки валидации, повторно отправить форму было уже нельзя. form-extra-events решает эту проблему, он предоставляет несколько новых типов событий формы, одно из которых гарантирует то, что форма отправляется, и этот процесс уже нельзя прервать. Кроме того, генерируются события начала и завершения отправки формы, что используется во всех остальных возможностях библиотеки.
Сайт-прототип
Для демонстрации полезности библиотеки, я набросал прототип типичного интернет-магазина на Bootstrap:
http://paulzi.ru/paulzi-form-site/
Из скриптов используем только jQuery, bootstrap и paulzi-form.all.js. В данном прототипе мы не используем ни строчки JS-кода написанного специально для сайта-прототипа.
Html5 form-атрибуты
В чём может пригодиться HTML5 form-атрибуты? Например, в корзине предполагается несколько действий над выбранными товарами — добавить в избранное, отправить на Email, скачать. Конечно, можно было бы сделать единый action, а в параметрах передавать, что конкретно нужно сделать. Но это некрасиво, т. к. порождает использование
switch($action)
, вместо того, чтобы сразу направить на конкретный action (например, в Yii2). А если вы откроете модальное окно, то увидите, что кнопку отправки пришлось сделать вне самой формы, тем не менее она продолжает функционировать, так как ей был указан атрибут form
. А самое главное, данные атрибуты сильно выручают в ситуациях, когда в большой форме нужно сделать маленькую форму, что стандарт HTML запрещает.
Не отправлять пустые поля
Теперь, обратим внимание на фильтр в каталоге. Если отметить галочкой «Intel Core i5» и отправить данную форму, то даже если другие поля не заполнены, мы всё равно получим длинную простыню из параметров после перехода:
?proce_from=&proce_to=&tdp_from=&tdp_from=&line[]=i5
Используя библиотеку, если добавить к форме атрибут data-submit-empty="false"
незаполненные поля не будут отправляться, и в результате получится более человекопонятный URL:
?line[]=i5
Блокировка повторной отправки
Рассмотрим форму заказа обратного звонка. Если вы сделаете двойной щелчок по кнопке отправки формы, форма у вас отправится дважды, и вам придёт два письма. Это неправильно, поэтому скрипт по-умолчанию блокирует возможность повторной отправки формы, до тех пор, пока запрос не выполнится. Пример корректной работы можно увидеть в форме «Написать нам». Регулируется это поведение путём установки атрибута
data-lock="false"
Индикация состояния отправки
Иногда, процесс отправки формы может занимать длительное время (отправка файла, процессороёмкий обработчик, медленный интернет), и если никак не отобразить, что форма отправляется, пользователь рано или поздно подумает, что либо он не нажал кнопку, либо что-то зависло, и нажмёт кнопку повторно. Пример такой формы — форма «Написать нам». В библиотеке предусмотрено несколько вариантов, в прототипе я использовал атрибуты
data-loading-text
и data-loading-icon
, которые изменяют текст кнопки и добавляют иконку. Также к форме и кнопке добавляются классы form-loading
и btn-loading
, это позволяет застилизовать состояние через CSS.
AJAX-отправка формы
Ну и самое главное, это возможность отправки формы через AJAX. Действительно, у нас в прототипе есть две формы в модальных окнах, логично при нажатии на кнопку отправки не осуществлять переходы между страницами, а просто закрыть модальное окно и вывести сообщение об успехе. И тут всё очень легко — добавляем атрибут
data-via="ajax"
, и вуаля, форма отправляется через AJAX. И мало того, те, кто хоть раз занимался передачей через AJAX файлов знает, что сделать это не так просто, т. к. поддержка отправки файлов появилось только начиная с XMLHttpRequrest 2. Для этого часто подключают сторонние библиотеки, которые часто требуют написания на серверной части особых обработчиков, хранить файлы во временной папке, а потом при отправке формы в отдельном запросе их оттуда забирать. В нашем же случае, об этом практически не надо задумываться — всё идёт также, как если бы форма отправлялась стандартным способом. При необходимости можно легко вывести процент отправленных данных, подцепившись на событие uploadprogress
.
Обработка AJAX-ответов
Посмотрим на пример посложнее — кнопка добавления в корзину на странице каталога. При нажатии на неё надо не просто выполнить запрос и вывести сообщение — нужно обновить краткий список содержимого корзины при наведении и обновить счётчик количества товаров в ней. Этот момент тоже учтён, и я постарался сделать максимально гибкий обработчик AJAX-ответов. Рассмотрим ответ, который приходит при нажатии на «В корзину» в прототипе:
<span class="badge" data-insert-context="document" data-insert-to=".cart-block .badge" data-insert-mode="replaceWith">43</span>
<div class="alert alert-success alert-dismissible" role="alert" data-insert-context="document" data-insert-to=".alert-fixed">
<button type="button" class="close" data-dismiss="alert"><span aria-hidden="true">×</span></button>
Товар добавлен в корзину!
</div>
<div class="cart-block-hover" data-insert-context="document" data-insert-to=".cart-block-hover" data-insert-mode="replaceWith">
<div class="row">
<div class="col-xs-6 text-left">AMD K6</div>
<div class="col-xs-6">10 шт.</div>
</div>
<div class="row">
<div class="col-xs-6 text-left">Intel Celeron</div>
<div class="col-xs-6">12 шт.</div>
</div>
<div class="row">
<div class="col-xs-6 text-left">Intel Core i7</div>
<div class="col-xs-6">1 шт.</div>
</div>
<div class="row cart-block-total">
<div class="col-xs-6 text-left">Итого:</div>
<div class="col-xs-6"><span class="price"><span>117 000</span> ?</span></div>
</div>
</div>
Как видите, ответ состоит из трёх html-элементов с атрибутами data-insert-to
, data-insert-context
, data-insert-mode
. Эти атрибуты определяют как и в какое место вставить каждый элемент, например, для первого span.badge
data-insert-context="document" data-insert-to=".cart-block .badge" data-insert-mode="replaceWith"
означает, что данным элементом нужно заменить элемент .cart-block .badge
и зона поиска данным селектором — весь документ. Всё это позволяет провести описанные выше действия без единой строчки кода.
Сценарии
Вернёмся на страницу корзины. Как уже было рассмотрено выше, на неё есть несколько кнопок, которые подразумевают разные действия. Например, при нажатии на "оформить" логично отправить форму стандартным механизмом, после чего осуществить редирект на страницу с информацией о заказе. А вот при нажатии на "В избранное", наоборот, лучше не уходить со страницы, а просто отправить форму через AJAX и вывести сообщение об успехе или ошибке. Для этого реализованы сценарии — для любой кнопки можно указать
data-via
атрибут, который укажет как отправлять форму при нажатии на неё.
Настройки
Если вам что-то не нравится, или возникают какие-либо конфликты, в библиотеке можно многое поменять через настройки. Например, нужно ли блокировать повторную отправку всех форм по-умолчанию. Все названия атрибутов также можно поменять.
Заключение
В итоге, подключив библиотеку размером 10 кб, вы получаете самое главное — нужно меньше писать код для разных мелких форм (а то и вовсе не писать), а в довесок получаете несколько приятных возможностей, такие как дополнительные события форм, iframe-транспорт, AJAX-отправка файлов и др.
Комментарии (24)
Alexufo
24.07.2016 00:59-3Отлично. Давно подобное напрашивается. Обязательно буду смотреть.
Офигеть. 5 часов в эфире и всего +6 голосов за статью. Года три назад сотня была бы точно.dfuse
24.07.2016 10:41+1Потому что года три назад никто ничего не знал про Angular, React и прочие, а также про HTML5 File Api, WhatWG Fetch и тд. Зачем в 2016 году библиотека по работе с формами — не ясно.
PaulZi
24.07.2016 11:21-3Разве Angular, React и HTML5 File API предоставляют возможность отправлять формы через AJAX, без кучи `new XMLHttpRequest()` для каждой формы? Даже если отмести все старые браузеры (а далеко не всегда это возможно) и выкинуть из библиотеки `ajax-iframe-transport` и `form-attributes-polyfill`, всё равно остаётся много полезного функционала — ajax отправка форм без единой строчки кода, стилизация состояния отправки, возможность навешивать обработчики отправки форм после других скриптов и др.
Если вы найдёте простой способ это реализовать на react и angular без написания аналогичного скрипта — будет очень интересно посмотреть.dfuse
24.07.2016 11:33Смотрите
var f = new FormData(), d = document, byId = d.getElementById; f.append('text', byId('text').value); // текстовые значения f.append('file', byId('file').files[0]); // файлы из формы f.append('file2', new File([JSON.stringify(...whatever...)], 'request.json', {type: 'application/json'})); // произвольное барахло fetch('url', {method: 'post', body: f}).then(...).catch(...);
Вот и все, и работает почти везде.
Про стилизацию я вообще молчу — она у всех разная, на реальном сайте я быстрее напишу CSS с нуля, чем буду накручивать любой коробочный. А любые визуальные фидбеки отправки я навешу в одно действие в .then и .catch.PaulZi
24.07.2016 11:39+1Я могу такой же короткий пример написать на ультра-старом jQuery, но ваш пример не решает и половины проблем, которая решает библиотека. А как только вы их решите, ваш ультра короткий и лаконичный код распухнет как минимум до 4 кбайт, который надо будет тащить из проекта в проект.
PS В конструктор FormData пожно передать саму форму, тогда append() вообще не надо.dfuse
24.07.2016 11:47+4jQuery еще подключить надо. А вышеописанное — стандартная библиотека, которая есть везде, совместимая с любым React/Angular/PlainVanilla.
Библиотека решает надуманные проблемы, ничего общего не имеющие с реальным миром и реальными сайтами. Любой серьезный проект так или иначе будет иметь код, отвечающий за весь трафик, включая формы. А для проекта на коленке сгодится и прямое использование стандартных средств браузера. Получается, ни туда, ни сюда библиотека.
Прошли те годы, когда подобное было актуально, лет 5 назад вы бы получили море звезд и хвалебных отзывов, но времена поменялись.Gitkan
24.07.2016 14:39Тут есть другой критерий полезности/нужности — насколько часто встречаются формы на современных сайтах? Если это форма постинга в блог или форма отправки заказа в интернет-магазине, то все задачи решаются соответствующими движками из коробки. Но если у разработчика специфический заказ и у него на каждой странице по несколько форм (какая-нибудь не к ночи будет сказано online-ERP система), то библиотека и может быть полезна.
PaulZi
24.07.2016 11:44-1По поводу стилизации, по умолчанию просто добавляются CSS-классы в форму — стилизуй как хочешь. Кроме того, есть поддержка js-шаблонов, и на крайний случай, всегда можно подцепиться на события `submitstart` и `submitend` и застилизовать произвольным образом. То есть ничего «коробочно» не навязывается.
dfuse
24.07.2016 11:50+3А как насчет валидации (клиентской и серверной), а также распихивания ошибок по полям? Вы поймите, прошли те времена, когда юзеры брали скрипт и впендюривали его на страницу, сейчас все живут в некой экосистеме, будь то Angular, React, Knockout, Ember или любая другая, где есть уже шаблоны, есть событийная модель и тд и тп.
hlogeon
24.07.2016 14:22Angular, React и HTML5 File API предоставляют возможность отправлять формы через AJAX
Не могу со 100% уверенностью сказать за остальные помимо Angular(ибо я занимаюсь back-end), но со 100% уверенностью говорю, что Angular позволяет. Более того, вполне себе позволяет создавать целые сервисы и директивы с формами с вытекающими отсюда плюшками в виде реюзабельности и наследования. Думаю, что React тоже это позволяет.
Varkus
24.07.2016 11:07когда читаю, как в очередной раз, вместо того, чтобы хорошо знать язык сайтокодеры подключают очередные файлы к странице — печалюсь: это не сайт перегружен всяким барахлом, а телефон у вас тупой, браузер медленный, пальцы кривые, рожей не вышел и т.д.
P.S. вероятно это будет уже 5й коммент подряд не одобренный администрацией.kesn
24.07.2016 11:27+2Если вы про библиотеку автора — то прошу пояснить, что же конкретно в ней плохо. В ней собраны юзкейсы, которые встречаются постоянно. Why not?
PaulZi
24.07.2016 11:27+1Что-то я не понял, что вы хотели сказать своим комментарием? Откройте какой-нибудь мега современный сайт на angular/react (например tinkoff.ru) и прочем и посмотрите сколько там скриптов подключается. Современные сайты действительно имеют много JS-кода, без него сейчас красивого и удобного сайта не сделать.
rockin
24.07.2016 19:04Правда? :)
Хотите историю из жизни и с конкретным примером?
Есть такой, не побоюсь этого слова, крупнейший интернет-магазин компьютерных комплектующих в РФ. Да, это Ситилинк. В рекламе не нуждается, сами понимаете.
И был у него нормальный, лёгкий, быстрый сайт. Логичное меню, картинки на своих местах.
Не знаю, что там случилось, и кому конкретно в голову ударила жидкость жёлтого цвета, но…
сейчас там «расчудесное», криво работающее, да ладно там криво — еле-еле работающее меню. которое выезжает когда не надо, уезжает когда не надо и перекрывает полстраницы.
А ещё там есть .js… пока её не пожали, весила она более мегабайта(!)
Сейчас — 890кб.
http://static.citilink.ru/build/js/citilink.build.min.js
И css на 290кб
http://static.citilink.ru/build/styles/global/citilink.build.css
Был красивый и удобный сайт. Стал некрасивый и жутко неудобный. JS стало больше в разы. А толку намного меньше
А при заходе на юлмарт грузится js сервиса аналитики размером в 1200кб
Куда мир катится, я хз. Такое чувство, что все только дома сайтики открывают, а домики все подключены оптикой.
Xtray
24.07.2016 20:14-3Если имеют много JS, это же не значит, что это хорошо. Удобный сайт спокойно может без JS обойтись. Ну, или самый минимум, десяток-другой строк кода, без каких-либо фреймворков. А красота — вообще субъективна, а потому — да, и красивый сайт тоже можно без JS сделать.
nazarpc
На сколько древним должен быть браузер чтобы не уметь отправлять файлы через XHR? Мне казалось, это уже в прошлом и не актуально.
PaulZi
http://caniuse.com/#feat=xhr2