12 июля 2011 в 12:44

Плагин для «живых» форм из песочницы

Статья посвящена плагину, который упрощающает жизнь client-side программиста.

При заполнении формы случается так, что учитывая введенные данные, форму нужно менять (прятать и показывать поля). Простейший пример: при заказе доставки товара пользователь выбрал «самовывоз», значит поля про адрес доставки можно спрятать, зато было бы здорово показать карту проезда для самовывоза.

И что дальше

Часто такая логика остается без реализации, однако если вы заботитесь о своих пользователях, то делать это нужно.

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

Второй подход это писать «портянки» javascript'а который всю эту логику реализует на стороне клиента. Особенно грустно писать «каскадную» логику типа «если ввели a в поле A, показать B, если ввели c в поле B, то показать D. Если в A ввели что-то другое, спрятать B, а потом D».

Оба варианта нельзя назвать удобными. Прежде всего потому что логика реализуется императивным стилем вместо уместного для таких случаев декларативного. Но выход есть!

Решение

В общем когда возникла потребность реализовывать еще одну такую форму, то было решено написать наконец плагин для jQuery который бы:
  • реагировал на заполнение полей формы
  • позволял описать стандартную логику типа показать, спрятать, добавить вариант в select в одну-две строки
  • сам бы заботился о каскадном выполнении действий и их откате
  • требовал бы для изучения пару минут


Получившийся плагин я назвал jQuery.grewform (типа растущая форма). Без разглагольствований – простейший пример. Опишем реализованную логику русским языком:
Если  #first имеет значение "show", то нужно показывать #p2. Если #second имеет значение "gogo", то нужно показывать #p3.

Теперь посмотрим как эта логика описана с использованием плагина:
$('form').grewform({

    //Если  #first имеет значение "show", то нужно показывать #p2
    '#first[value=show]':{
        show:'#p2'
    },

    //Если #second имеет значение "gogo", то нужно показывать #p3
    '#second[value=gogo]':{
        show:'#p3'
    }

})


Подробнее

Далее идёт подробное описание работы плагина. Если вам всё более-менее понятно из приведенного выше примерчика, можете пропустить этот раздел статьи и перейти к описанию возможностей.
Поведение плагина задается набором правил. Каждое правило состоит из терма и набора действий. После инициализации, а так при каждом изменении формы плагин проверяет терм каждого правила. При проверке определяется соответствие терма текущему состоянию формы. Каждое правило может оказаться в одном из трех положений:
  1. при прошлой проверке терм не соответствовал состоянию, а теперь соответствует
  2. при прошлой проверке терм соответствовал состоянию, а теперь не соответствует
  3. соответствие терма состоянию формы не изменилось

В первом случае правило помечается как сработавшее, элементы DOM описанные в терме (по сути изменение этих элементов привело к срабатыванию правила) получают метку (добавляется класс) показывающую что на них «висит» сработавшее правило такое-то. После чего запускаются по очереди все описанные в правиле действия. Второй случай более интересен. Аналогично, правило помечается как уже не сработавшее, пометки с элементов удаляются. После чего запускаются негативы действий описанных в правиле (показали что-то? Теперь спрячем!), причем если в процессе в DOM прячутся или удаляются какие-то элементы, то сначала проверяется
не «висит» ли на них каких-либо правил (см. случай №1), если это так то запускается процесс «отката» для этих правил (работает как случай №2). В третьем случае ничего не происходит.
Стоит отметить, что про проверке соответствия термов учитываются только видимые элементы формы.

Возможности

Выражение по которому определяется не пора ли запускать действия (терм) это CSS/jQuery-совместимый селектор. Если в форме находятся элементы соответствующие селектору, то запускаются действия этого правила. Иногда нужно чтобы правило сработало только при двойном (тройном, четверном...) условии, для этого можно использовать функцию AND (пример). Это единственное изменение синтаксиса, а точнее дополнение.
В первом примере используется селектор [value=show]. Особо хорошо знающие jQuery справедливо скажут, что такие селекторы не должны работать если value был изменен в процессе выполнения кода. Однако плагин поддерживает актуальное состояние атрибута value для всех <input/>, соответсвенно всё работает как надо.
Перейдём к описанию разновидностей действий. Вот их список с описанием синтаксиса:
{
    show:'elements_selector',   //показать элементы (slideDown; slideUp при откате)
    hide:'elements_selector',    //спрятать элементы (slideUp; slideDown при откате)
    disable:'elements_selector' //добавляет disabled="disabled" к атрибутам элемента (убирает при откате)
    enable:'elements_selector' //убирает атрибут элемента disabled  (добавляет disabled="disabled" при откате)
    check:'elements_selector'  //добавляет checked="checked" к атрибутам элемента (убирает при откате)
    uncheck:'elements_selector' //убирает атрибут элемента checked  (добавляет checked="checked" при откате)
    set_value:'elements_selector',  //задаёт value для <input>, для <select> добавляет selected="selected" у соответствующего <option>
    add_options:            //добавляет <option> в <select>
    {
        '<select> selector':{
                      'value_1':'display_value_1', //соответствует <option value="1">display_value_1</option>
                      'value_2':'display_value_2',
              ...
                }
        or
        '<select> selector': function   //должна возвращать объект (формат - {'value_1':'display_value_1',...})
    },
    custom:     //для особых случаев
    {
        match:function,       //будет вызвана при срабатывании правила
        unmatch:function,   //будет вызвана при откате
    }
}


Как было сказано выше описываются только позитивные события, т.е. только те что должны произойти при срабатывании правила. Действия отката вычисляются автоматически.
Как видно почти все действия имеют следующий синтаксис:
название_действия:'селектор элементов'
Синтаксис селектора так-же полностью CSS/jQuery-совместимый. Над элементами найденными по селетору будет произведено указанное действие. Собственно все действия описаны выше в коде.

Под конец самый большой и мощный пример – форма заказа психологической консультации =)

Need help

Я начал писать этот плагин 2 недели назад и мы уже используем его в 2 формах на продакшне. Буду очень благодарен, если вы попробуете этот плагин и расскажите чего вам не хватает.

Ссылки:


© картинка взята из книги Code Complete
+110
275
H1D 11,5

комментарии (67)

0
fealaer, #
Симпатично. Только вот нет ни слова про лицензию.

Под какой лицензией можно использовать?
0
H1D, #
Да, позабыл. При следующем коммите добавлю лицензию MIT.
+1
creadone, #
Программистам, наверное, этот плагин будет хорошим подспорьем в работе. Вот только пользователи сильно удивятся, когда при выборе определенного значения откроется блок с еще «стотысячами» полей.

Может быть имеет смысл предусмотреть какую-то индикацию прогресса заполнения полей на уровне плагина?
0
H1D, #
Ну я пока два варианта вижу.
— подсветка появляющихся элементов
— добавить каждому правилу необязательное поле «user message», при срабатывании правила будет появляется сообщение
0
Kenny_dn, #
Я думаю лучше будет сделать что-то вроде индикации этапа 1→2→3, с подсветкой в зависимости от того завершил клиент заполнять часть формы или нет.
0
chaZmich, #
Выглядит небезынтересно. Вешать плагин можно только на форму или любой элемент подойдет?
0
H1D, #
на любой
0
AGrebinchukov, #
Хорошая работа, а довелось ли сравнивать с сущестующими библиотеками, такими как knockout.js? На мой взгляд отличная реализация паттерна MVV на JavaScript
0
Olostan, #
мне особо понравилась там observable pattern.

И да, очень пересекается имхо с этим плагином, разве что плагин в статье как-то полегче выглядит, но и сходу больше возможностей.
+3
H1D, #
knockout.js очень хорош. Но мне не очень нравится что логика задается через атрибуты элементов, то-есть получается жуткая каша в которой одновременно описана и структура документа и логика обработки.
0
VGrigoryev, #
Спасибо, попробую в ближайшее время. Недавно была такая задача, решал методом написания «простыни» :)
0
alexevil, #
Спасибо, буквально на днях возникла необходимость в таком функционале )
+1
Grundiss, #
Не соглашусь насчет визарда. Мне как пользователю гораздо удобнее заполнить 10 махоньких формочек, чем одну большую, которая к тому же еще и меняется как-то динамически.
0
valmont, #
впечатал show, впечатал gogo… убрал show, всё скрылось, впечатал show и… оппа моё gogo на месте. баг или фича? :)
+3
H1D, #
фича конечно. Зачем прятать то что пользователь уже вводил.
0
valmont, #
но если я самостоятельно стёр то, что впечатал, возможно мне и не нужны больше те данные. для чего их хранить?
0
Alexeyco, #
Коллбеки решают…
0
s0rr0w, #
Там другой «фиче-баг» назревает. Пользователь ввел данные в поле1, потом в поле2. Потом в поле1 данные стер и нажал на сабмит. На сервер уйдет поле1="" и поле2=«предыдущий текст».

Это, в принципе, не сильно парит, но можно будет нарваться на ситуацию, когда на сервер придет «задизейбленный» чекбокс с активным значением, который потом сломает что-нибудь.
+1
H1D, #
Это вопрос корректной реализации на стороне сервера. По идее если поле скрыто, то его значение не важно и должно быть проигнорировано.

Возвращаясь к примеру в начале статьи, если выбран «самовывоз» то обрабатывать как-то «адрес доставки» нет смысла.
0
s0rr0w, #
Это звучит неплохо, пока вам не придется сохранять 500 различных полей, половина из которых является динамическими формами. Вот тогда понадобится единый метод управления построением форм и ее валидации. Тут SVARX выглядит гораздо более привлекательным идеологически.

+1
H1D, #
SVARX занимается только валидацией форм
описанный плагин занимается только динамическим изменением форм

Но вообще возможно и есть смысл воткнуть опцию типа «autoclear».
0
s0rr0w, #
Динамическое изменение форм и последующая валидация — последовательно идущие действия. Смотрите шире, не решайте только одну задачу из всего комплекса. На SVARX легче легкого навесить функционал динамического изменения форм, так как там присутствует достаточное количество информации для реализации задуманного. Так понятнее? Но вдобавок, SVARX решает и проблему валидации, что ваш плагин делать не умеет.
+5
H1D, #
Предпочитаю не делать подобного
0
valmont, #
проверка на клиенте, затем на сервере это достаточно стандартная процедура валидации данных.
+1
Akenfold, #
Это совсем не проблема. При скрытии полей им просто нужно добавлять атрибут disabled. Значения отключённых полей на сервер не посылаются.

<form action="" method="get">
    <p><input name="foo" type="text" value="1" /></p>
    <p><input disabled="disabled" name="bar" type="text" value="2" /></p>
    <p><input type="submit" /></p>
</form>
0
s0rr0w, #
В текущей реализации дезактивации элементов формы не наступает. Я про это состояние кода говорил.
0
valmont, #
Именно о это я имел ввиду, что на сервер будут поступать «лишние» значения полей.
0
H1D, #
Данные нужны не вам, а серверу который будет обрабатывать введенные данные =)
Если логикой задано скрывание поля, наверное значение этого поля уже не важно и оно будет проигнорировано на стороне сервера.
+2
xaxaTyH, #
Вполне предсказуемое поведение.
+2
max_rip, #
Есть же zforms
0
H1D, #
не знал про него, спасибо.
0
max_rip, #
Минусы у него конечно тоже есть, но плюсов больше.
–3
s0rr0w, #
Программисты такие программисты…

Вопрос автору плагина. Пользователь наколбасил форму, сохранил. Как ее потом отредактировать?
0
H1D, #
Не уверен что понял описанный сценарий, но вообще если с сервера придёт форма в которой уже заданы значения некоторых полей, то ничего не поломается. Плагин пробегает по всем правилам после загрузки DOM.
Возможно вы упустили что плагин не добавляет или удаляет поля, прячет и показывает.
+1
s0rr0w, #
Я ничего не упустил. Вы просто не все ситуации предусмотрели.

Открываем 4й пример. Выбираем Form, Type, Number. Постим данные на сервер. Как будет выглядеть эта же форма с уже заполненными данными, если захочется пользователю потом что-то изменить?

Опции Type и Number заполняются скриптом… И им нужно будет присвоить нужные значения. Как?
0
H1D, #
Ну да, со случаями когда добавляются элементы DOM придётся ручками… Тут вы правы.
–1
s0rr0w, #
Хабр такой Хабр. Нашел принципиальную архитектурную багу, а это, судя по минусам в комменте, оказывается плохо… :)
+1
H1D, #
ну главное меня убедили. Я добавлю опцию для проставления disabled=«disabled».
Так что мир станет лучше благодаря вам :)

Минусуют за то что суть вопроса в вашем первом коменте приходится долго искать.
+3
1nd1go, #
Годный плагин. Но я бы еще дизаблил скрытые значения, или сделал возможность для этого.
0
H1D, #
>дизаблил скрытые значения
— всмысле затирать значения перед отправкой формы?
+2
1nd1go, #
нет, всмысле выставлял disabled=«disabled»
+2
fealaer, #
1nd1go предлагает использовать «disabled», потому что такие поля не отправляются на сервер. Необходимость очистке полей отпадает.
–2
dio, #
Полезная вещь! В закладки.
А автора поздравляю с первой публикацией :)
–1
Frankfurt, #
Меня например раздражает что откуда ни возмись появляются новые поля ввода. Нужно этот момент додумать.
+6
habracut, #
Выпейте валерьянки например, успокойтесь.
0
qmax, #
usecase:
ввожу название товара (выбираю из списка),
клиент отсылает запрос на сервер и если он есть в наличии — открывает форму оплаты,
а если нету — форму уведомления о поступлении.

0
H1D, #
Дейстие «custom» к вашим улугам. Причем вам необязательно самостоятельно писать прятать/показывать поля. В custom правиле можно описать только отправку запроса и в случае положительного результата изменять форму так, чтобы сработали правила которые покажут форму оплаты товара.
0
Apx, #
Всё же немножко стрёмно с сохранением введённых данных…
Если логика такова, что всё что невидимо — является ненужным, то посылать серверу ещё кучу «ненужных данных», чтобы сервер их сортировал, убирал мусор и понимал что и как сохранять то:
1) Расходы траффика больше
2) Становится запутанной серверная часть, чего вроде бы пытались избежать
3) Белиберда в данных.
Например фирма регистрируется на сайте. Человек вначале затупил ввёл свой домашний адрес, потом вспомнил что он представитель конторы, выбрал в дропдауне Office вместо Home, ввёл адрес офиса и засабмитил, а сервер видит что поле с домашним адресом тоже не пустое и сохраняет и его, в результате офис имеет непонятно какой адрес. Не сильно кошерно как по мне.
Может всё же чистить данные?
А если вопрос в юзабилити, то тогда просто моно введённые данные хранить, а у инпутов просто убивать аттрибут name и тогда в сабмит они не попадут, и северер перегружать какой то заумной логикой не понадобится.
0
H1D, #
1nd1go предложил вариант лучше. У спрятанных полей ставить disabled=«disabled», соответственно они не уйдут на сервер при отправке формы.
0
Apx, #
Ну как говорится алтернатива есть =) На счёт Disabled согласен, оно работает стабильно, а вот с name я не до конца уверен можно ли его менять/удалять. Точно помню что вот с полем type=password нельзя тип поменять, как с неймом не помню (но помоему можно).

В целом мне кажется плагин в хозяйстве пригодится, во всяком случае какие то тривиальные вещи сверстать будет куда быстрее, чем писать с 0, так что я себе кладу на полку, дабы не забыть =)
0
Apx, #
Всё же немножко стрёмно с сохранением введённых данных…
Если логика такова, что всё что невидимо — является ненужным, то посылать серверу ещё кучу «ненужных данных», чтобы сервер их сортировал, убирал мусор и понимал что и как сохранять то:
1) Расходы траффика больше
2) Становится запутанной серверная часть, чего вроде бы пытались избежать
3) Белиберда в данных.
Например фирма регистрируется на сайте. Человек вначале затупил ввёл свой домашний адрес, потом вспомнил что он представитель конторы, выбрал в дропдауне Office вместо Home, ввёл адрес офиса и засабмитил, а сервер видит что поле с домашним адресом тоже не пустое и сохраняет и его, в результате офис имеет непонятно какой адрес. Не сильно кошерно как по мне.
Может всё же чистить данные?
А если вопрос в юзабилити, то тогда просто моно введённые данные хранить, а у инпутов просто убивать аттрибут name и тогда в сабмит они не попадут, и северер перегружать какой то заумной логикой не понадобится.
0
Apx, #
Извиняюсь за лабуду с каментами… Глюканула форма…
0
Slash, #
достаточно ставить аттрибут disabled и данные не будут отправлены на сервер.
0
Slash, #
так и не понятно как делать валидацию. например я использую серверный конструктор форм, могу по идее сразу всю форму сделать, а потом вашим плагин модифицировать. но если, например форма скрыта, то клиентская валидация, как правила биндить и снимать с поля? или я чего-то не понимаю?
0
H1D, #
Валидация — смежная задача, но плагин ею не занимается. Так сделано умышленно, чтобы не случилось ситуации указанной в этом этом комменте.
+1
MilesD, #
Столкнулся недавно с подобной задачей, несколько усложненной… Нужны были динамические и многошаговые формы, причем в зависимости от введенных параметров на предыдущих шагах, изменялось поведение последущих форм.
Проблему решил с помощью шаблонизатора на стороне клиента (плагин jQuery templates) habrahabr.ru/blogs/jquery/112843/

Кратко:
Есть структура вложенных шаблонов. От корневого шаблона до шаблонов отдельных элементов управления. Вся логика отображения элементов форм в том или ином случае задается в шаблонах.
Есть данные, которые загружаются в шаблон, и которыми оперирует шаблоны (использует их в логике, подставляет их в формы).

Теперь конкретно к задаче:
Нажали на кнопочку (должны появиться дополнительные элементы управления, поменяться текстовки, что-то еще)… Обработчик события обновляет данные и всего-то надо сделать ререндеринг корневого или группы вложенных шаблонов. Данные изменены, результат выполнения рендеринга шаблонов будет иным. Благодаря встроенным возможностям кэширования, ререндеринг происходит моментально.

минусы:
— плагин имеет статус beta, документации мало. Набил шишек.
— ошибки в шаблонах сложно находить.
— есть еще
плюсы:
+ наглядная и понятная логика в шаблонах.
+ огромная экономия трудозатрат на разработку сложных динамических форм. Думается, любыми другими известными мне средствами и плагинами моя задача была бы невыполнима за данное мне время.
++ до знакомства с jQuery templates решал данные задачи на чистом jQuery и сталкивался с проблемой, что при изменении объектов формы без перезагрузки объекта form давало множество побочних эффектов. Проблемы костылями решались — нехороший осадок остался. При использовании jQuery templates перегрушая корневой шаблон мы гарантируем, что объект form уничтожается и создается заново с видоизмененными элементами управления согласно логике. Побочних эффектов нет.
0
H1D, #
спасибо, интересно, но на пальцах сложно понять как там у вас всё работает =)
0
MilesD, #
если интерес сохранится — пишите, расскажу поподробнее.
Хотя, если вы решаете задачу собственными инструментами — сложно согласиться на альтернативу.
Я бы задумался над таким вопросом: «А что если задача многократно усложнится?», т.е. появятся навороченные элементы управления на форме, например: динамические таблицы для ввода списков данных, с контроллами для добавления/удаления элементов или с ajax запросами по каждому элементу для ввода; усложните формы, как по количеству элементов управления, так и по связям между ними.
Будет ли ваше решение так же эффективно решать более сложные задачи, будет ли код легко читаем, а логика прозрачна?
В своем проекте, где был использован jQuery Templates, логика существенно усложнилась к моменту релиза (сложно было все в ТЗ предусмотреть)… Данный шаблонизатор позволил успешно бороться с возросшей сложностью)
0
lukianol, #
А какая совместимость с браузерами у этого плагина?

IE8 говорит

Webpage error details

User Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 1.1.4322; MS-RTC LM 8; .NET4.0C; .NET4.0E)
Timestamp: Wed, 13 Jul 2011 14:37:06 UTC

Message: Object doesn't support this property or method
Line: 115
Char: 73
Code: 0
URI: h1d-demos.appspot.com/static/jquery-grewform/jquery.grewform.js

0
H1D, #
спасибо! У меня тоже повторилось, скоро починю.
0
AndreDerMorgenstern, #
Я проверил плагин на реальном проекте. Действительно хорошая помощь в деле создания сложных форм.

Я нашел ряд ошибок (версия 0.0.2):

Строка 14, ошибка в названии переменной (code вместо codes):
jQuery('input,textarea').live('keyup change',function(e) {
        var codes= [33,34,36,35,45,38,40,37,39]//arrows and others
        if(e.keyCode && jQuery.inArray(e.keyCode,code)<0)//skip this keyUps to let this keys work as expected
            jQuery(this).attr('value',this.value)
    });


Строки 115, 477, вызов функции trim объекта String. Этот метод ввели совсем недавно (если не ошибаюсь, в версии 1.8.1, Firefox 3.5), и далеко не во всех популярных браузерах (особенно понимаете где) он работает. Я бы заменил на jQuery.trim().

Так же есть вызовы функции debug, которая, к тому же, использует console, что вызывает ошибки в браузерах, где консоли нет.
0
H1D, #
оу!!! Спасибо большое за фидбек!!! Кое-про что знаю, скоро поправлю.
0
ViGilant, #
эх, жаль не хочет работать с dreamhelg.ru/2009/11/fancy-ajax-contact-form/
понять почему знаний не хватает( не работает именно внутри контейнера формы, видно мешает какой-то из ее родных скриптов
0
mihdan, #
Как я могу добавить свои селекторы? Например, мне нужно при выборе любого ненулевого значения из списка (select) показать другой список (select)? Спасибо
0
H1D, #
Не нужен для этого свой селектор: jsfiddle.net/kHgsp/1/
0
mihdan, #
С чекбоксами все понятно, как быть с селектами?
0
H1D, #
при чем тут чекбоксы? У вас магически не та ссылка открылась

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Curiosity начал первый этап терраформинга
Три профессиональные деформации айтишников
Солнечная система на LibCanvas