一个clojurescript库,可为试剂提供表单数据绑定,请参见此处的实时演示。
该库使用试剂原子作为文档存储。组件使用:field属性绑定到文档。该键将用于确定应如何绑定特定类型的组件类型。该组件还必须提供一个唯一的:id属性,该属性用于将其与文档相关联。尽管图书馆旨在使用Twitter Bootstrap使用,但对您创建的组件类型的类型相当不可知。
:id可以是一个关键字,例如: {:id :foo} ,或一个关键字路径{:id :foo.bar} ,将映射到{:foo {:bar "value"}} 。另外,您可以明确指定矢量路径[:foo 0 :bar] 。
默认情况下,组件值是文档字段的值,但是所有组件都支持:in-fn和:out-fn函数属性。 :in-fn接受当前的文档值并返回组件中要显示的内容。 :out-fn接受组件值并返回文档中要存储的内容。
开箱即用的支持以下类型的字段:
输入字段可以是类型:text , :numeric , :range , :password , :email和:textarea 。输入的行为就像常规HTML输入一样,并在触发:on-change事件时更新文档状态。
[ :input.form-control { :field :text :id :first-name }]
[ :input.form-control { :field :numeric :id :age }]输入字段可以具有可选的:fmt属性,该属性可以为值提供格式字符串:
[ :input.form-control
{ :field :numeric :fmt " %.2f " :id :bmi :disabled true }]数字输入支持HTML 5数字输入的属性:
[ :input
{ :field :numeric
:id :volume
:fmt " %.2f "
:step " 0.1 "
:min 0
:max 10 }]Typeahead字段使用:data-source键。该控件使用输入元素来处理用户输入,并将选择列表作为包含一个或多个列表项目元素的无序列表元素。用户可以使用键使用键:Input-class,:list-class和:item-class的CSS类指定用于渲染这些元素的CSS类。用户可以另外指定CSS类,以使用以下方式处理当前选择的突出显示。参考CSS类包含在资源/公共/CSS/Reagent-forms.css文件中。
( defn friend-source [text]
( filter
#( -> % ( .toLowerCase %) ( .indexOf text) ( > -1 ))
[ " Alice " " Alan " " Bob " " Beth " " Jim " " Jane " " Kim " " Rob " " Zoe " ]))
[ :div { :field :typeahead
:id :ta
:input-placeholder " pick a friend "
:data-source friend-source
:input-class " form-control "
:list-class " typeahead-list "
:item-class " typeahead-item "
:highlight-class " highlighted " }]Typeahead字段支持鼠标和键盘选择。
您可以使输入的显示值与文档中存储的值不同。您需要指定:out-fn ,a :result-fn and oparionally :in-fn 。 :data-source需要返回矢量[display-value stored-value] 。
( defn people-source [people]
( fn [text]
( ->> people
( filter #( -> ( :name %)
( .toLowerCase )
( .indexOf text)
( > -1 )))
( mapv #( vector ( :name %) ( :num %))))))
[ :div { :field :typeahead
:data-source ( people-source people)
:in-fn ( fn [num]
[( :name ( first ( filter #( = num ( :num %)) people))) num])
:out-fn ( fn [[name num]] num)
:result-fn ( fn [[name num]] name)
:id :author.num }]]]如果:data-source在传递关键字时使用完整选项列表响应:all将显示“箭头键”。
:selections属性以传递用于保存选择的原子。这提供了使用Typeahead文本获取列表的选项 - 如果AJAX响应处理程序设置了原子,则列表将弹出。
如果提供,则:get-index函数将确保当列表弹出时突出显示所选项目。
演示页面的“源代码”中提供了一个完整的示例。
复选框字段创建一个复选框元素:
[ :div.row
[ :div.col-md-2 " does data binding make you happy? " ]
[ :div.col-md-5
[ :input.form-control { :field :checkbox :id :happy-bindings }]]]复选框接受可选:checked属性。设置时,将选择复选框,并将文档路径指向:id键将设置为true 。
[ :div.row
[ :div.col-md-2 " does data binding make you happy? " ]
[ :div.col-md-5
[ :input.form-control { :field :checkbox :id :happy-bindings :checked true }]]]范围控件使用:min和:max键创建HTML范围输入:
[ :input.form-control
{ :field :range :min 10 :max 100 :id :some-range }]无线电按钮不使用:id键,因为它必须是唯一的,并且使用:name属性进行分组。 :value属性用于指示保存到文档的值:
[ :input { :field :radio :value :a :name :radioselection }]
[ :input { :field :radio :value :b :name :radioselection }]
[ :input { :field :radio :value :c :name :radioselection }]单选按钮接受可选:checked属性。设置时,将选择复选框,并将文档路径指向:name键将设置为true 。
[ :input { :field :radio :value :a :name :radioselection }]
[ :input { :field :radio :value :b :name :radioselection :checked true }]
[ :input { :field :radio :value :c :name :radioselection }]文件字段绑定<input type="file"/>的File对象。
[ :input { :field :file :type :file }]与文件相同,除了它与<input type="file" multiple/>一起使用并绑定整个FileList对象。
[ :input { :field :files :type :file :multiple true }]列表字段包含子元素的值,其值在选择文档中填充的元素。子元素必须各自具有:key属性指向文档中将保存的值。元素的值必须是关键字。
元素可以具有可选:visible?指向谓词功能的关键字。该函数应接受文档并返回布尔值,指示是否应显示该字段。
:list字段用于创建HTML select包含option子元素的元素:
[ :select.form-control { :field :list :id :many-options }
[ :option { :key :foo } " foo " ]
[ :option { :key :bar } " bar " ]
[ :option { :key :baz } " baz " ]]
( def months
[ " January " " February " " March " " April " " May " " June "
" July " " August " " September " " October " " November " " December " ])
[ :select { :field :list :id :dob.day }
( for [i ( range 1 32 )]
[ :option
{ :key ( keyword ( str i))
:visible? #( let [month ( get-in % [ :dob :month ])]
( cond
( < i 29 ) true
( < i 31 ) ( not= month :February )
( = i 31 ) ( some #{month} [ :January :March :May :July :August :October :December ])
:else false ))}
i])]
[ :select { :field :list :id :dob.month }
( for [month months]
[ :option { :key ( keyword month)} month])]
[ :select { :field :list :id :dob.year }
( for [i ( range 1950 ( inc ( .getFullYear ( js/Date. ))))]
[ :option { :key ( keyword ( str i))} i])]单选项字段的行为类似于列表,但支持不同类型的元素,并允许取消选择字段:
[ :h3 " single-select buttons " ]
[ :div.btn-group { :field :single-select :id :unique-position }
[ :button.btn.btn-default { :key :left } " Left " ]
[ :button.btn.btn-default { :key :middle } " Middle " ]
[ :button.btn.btn-default { :key :right } " Right " ]]
[ :h3 " single-select list " ]
[ :ul.list-group { :field :single-select :id :pick-one }
[ :li.list-group-item { :key :foo } " foo " ]
[ :li.list-group-item { :key :bar } " bar " ]
[ :li.list-group-item { :key :baz } " baz " ]]多选字段允许在文档中选择和设置多个值:
[ :h3 " multi-select list " ]
[ :div.btn-group { :field :multi-select :id :position }
[ :button.btn.btn-default { :key :left } " Left " ]
[ :button.btn.btn-default { :key :middle } " Middle " ]
[ :button.btn.btn-default { :key :right } " Right " ]]标签可以使用:id属性与文档中的键关联,并将在该密钥处显示值。 lables可以具有可选的:preamble和:postamble键,其文本将分别在值之前和之后呈现。也可以使用分配给:fmt键的格式函数来解释该值。 :placeholder符密钥可用于提供在没有价值的情况下将显示的文本:
[ :label { :field :label :id :volume }]
[ :label { :field :label :preamble " the value is: " :id :volume }]
[ :label { :field :label :preamble " the value is: " :postamble " ml " :id :volume }]
[ :label { :field :label :preamble " the value is: " :postamble " ml " :placeholder " N/A " :id :volume }]
[ :label { :field :label :preamble " the value is: " :id :volume :fmt ( fn [v] ( if v ( str v " ml " ) " unknown " )}]警报绑定到触发警报的字段的ID,并且可以具有可选的:event键。事件密钥应指向返回布尔值的函数。
可选:closeable? true/false如果应该渲染关闭按钮(默认为true),则可以提供:closeable? true/false 。
提供事件时,只要事件返回真实,就会呈现警报的主体:
[ :input { :field :text :id :first-name }]
[ :div.alert.alert-success { :field :alert :id :last-name :event empty?} " first name is empty! " ]如果不提供事件,则每当ID的值不为空并显示值时显示警报:
( def doc ( atom {}))
; ;define an alert that watches the `:errors.first-name` key for errors
[ :div.alert.alert-danger { :field :alert :id :errors.first-name }]
; ;trigger the alert by setting the error key
[ :button.btn.btn-default
{ :on-click
#( if ( empty? ( :first-name @doc))
( swap! doc assoc-in [ :errors :first-name ] " first name is empty! " ))}
" save " ][ :div { :field :datepicker :id :birthday :date-format " yyyy/mm/dd " :inline true }]使用以下格式将日期存储在文档中:
{ :year 2014 :month 11 :day 24 } DatePicker还可以选择一个可选的:auto-close?键,表明当一天点击一天时,应关闭它。此默认为false 。
可以使用:date-format键设置日期格式:
{ :field :datepicker :id :date :date-format " yyyy/mm/dd " } :date-format还可以指向返回格式日期的函数:
{ :field :datepicker
:id :date
:date-format ( fn [{ :keys [year month day]}] ( str year " / " month " / " day))}以上内容与:save-fn键一起有用,该密钥允许您提供自定义功能以保存值。例如,如果要使用JavaScript日期对象,则可以执行以下操作:
[ :div.input-group.date.datepicker.clickable
{ :field :datepicker
:id :reminder
:date-format ( fn [date]
( str ( .getDate date) " / "
( inc ( .getMonth date)) " / "
( .getFullYear date)))
:save-fn ( fn [current-date { :keys [year month day]}]
( if current-date
( doto ( js/Date. )
( .setFullYear year)
( .setMonth ( dec month))
( .setDate day)
( .setHours ( .getHours current-date))
( .setMinutes ( .getMinutes current-date)))
( js/Date. year ( dec month) day)))
:auto-close? true }]请注意,您需要在更新中返回新的日期对象,以使组件重新粉刷。
DatePicker采用可选的:lang键,您可以使用该键来设置DatePicker的位置。目前有英语,俄语,德语,法语,西班牙语,葡萄牙语,芬兰语和荷兰语内置的荷兰语。使用内置语言通行证:lang带有关键字,如下表:
| 语言 | 关键词 |
|---|---|
| 英语 | :en-US (默认) |
| 俄语 | :ru-RU |
| 德语 | :de-DE |
| 法语 | :fr-FR |
| 西班牙语 | :es-ES |
| 葡萄牙语 | :pt-PT |
| 芬兰 | :fi-FI |
| 荷兰 | :nl-NL |
使用内置语言语言环境的示例:
{ :field :datepicker :id :date :date-format " yyyy/mm/dd " :inline true :lang :ru-RU }您还可以为datepicker提供自定义的网站图像。 :first-day标记本周的第一天,从周日开始为0。必须指定所有钥匙。
使用自定义语言环境哈希映射的示例:
{ :field :datepicker :id :date :date-format " yyyy/mm/dd " :inline true :lang
{ :days [ " First " " Second " " Third " " Fourth " " Fifth " " Sixth " " Seventh " ]
:days-short [ " 1st " " 2nd " " 3rd " " 4th " " 5th " " 6th " " 7th " ]
:months [ " Month-one " " Month-two " " Month-three " " Month-four " " Month-five " " Month-six "
" Month-seven " " Month-eight " " Month-nine " " Month-ten " " Month-eleven " " Month-twelve " ]
:months-short [ " M1 " " M2 " " M3 " " M4 " " M5 " " M6 " " M7 " " M8 " " M9 " " M10 " " M11 " " M12 " ]
:first-day 0 }} datepicker需要额外的CSS才能正确渲染。默认的CSS在资源路径中的reagent-forms.css中提供。只需确保它包含在页面上即可。可以使用以下方式读取文件:
( -> " reagent-forms.css " clojure.java.io/resource slurp)容器元素可用于分组不同的元素。该容器可用于设置多个元素的可见性。
[ :div.form-group
{ :field :container
:visible? #( :show-name? %)}
[ :input { :field :text :id :first-name }]
[ :input { :field :text :id :last-name }]]可以使用:validator关键字将验证器函数连接到组件。此功能接受文档的当前状态,并返回将附加到该元素的类集合:
[ :input
{ :field :text
:id :person.name.first
:validator ( fn [doc]
( when ( -> doc :person :name :first empty?)
[ " error " ]))}]组件可以提供可选的:visible?关键在其属性指向决策功能的属性中。期望该函数采用文档的当前价值并产生真实的值,以决定是否应渲染组件,例如:
( def form
[ :div
[ :input { :field :text
:id :foo }]
[ :input { :field :text
:visible? ( fn [doc] ( empty? ( :foo doc)))
:id :bar }]]):在您需要对组件属性进行任意更新的情况下,可以使用:set-attributes键。密钥必须指向一个函数,该函数接受文档的当前值以及组件的属性映射。该功能必须返回更新的属性映射:
[ :div
[ :input { :field :text
:id :person.name.first
:validator ( fn [doc]
( when ( = " Bob " ( -> doc :person :name :first ))
[ " error " ]))}]
[ :input { :field :text
:id :person.name.last
:set-attributes ( fn [doc attrs]
( assoc attrs :disabled ( = " Bob " ( -> doc :person :name :first ))))}]]上面的示例禁用姓氏输入时,当名称输入的值为“ Bob”时。
田间组件的行为就像其他任何试剂组件一样,可以自由地将其混合。一个完整的表单示例可以在下面看到。
形式元素可以使用.作为路径分离器。例如,以下组件[:input {:field :text :id :person.first-name}]绑定到状态arom {:person {:first-name <field-value>}}的以下路径
( defn row [label input]
[ :div.row
[ :div.col-md-2 [ :label label]]
[ :div.col-md-5 input]])
( def form-template
[ :div
( row " first name " [ :input { :field :text :id :first-name }])
( row " last name " [ :input { :field :text :id :last-name }])
( row " age " [ :input { :field :numeric :id :age }])
( row " email " [ :input { :field :email :id :email }])
( row " comments " [ :textarea { :field :textarea :id :comments }])])重要说明
热切评估模板,您应始终将助手函数称为上面的示例,而不是将它们放入向量。当bind-fields来编译模板时,这些试剂将被试剂组件替换。
创建表单模板后,它可以使用bind-fields函数绑定到文档:
( ns myform.core
( :require [reagent-forms.core :refer [bind-fields]]
[reagent.core :as r]))
( defn form []
( let [doc ( r/atom {})]
( fn []
[ :div
[ :div.page-header [ :h1 " Reagent Form " ]]
[bind-fields form-template doc]
[ :label ( str @doc)]])))
( reagent/render-component [form] ( .getElementById js/document " container " ))该表单可以用填充的文档初始化,并且该字段将以那里的值初始化:
( def form-template
[ :div
( row " first name "
[ :input.form-control { :field :text :id :first-name }])
( row " last name "
[ :input.form-control { :field :text :id :last-name }])
( row " age "
[ :input.form-control { :field :numeric :id :age }])
( row " email "
[ :input.form-control { :field :email :id :email }])
( row " comments "
[ :textarea.form-control { :field :textarea :id :comments }])])
( defn form []
( let [doc ( atom { :first-name " John " :last-name " Doe " :age 35 })]
( fn []
[ :div
[ :div.page-header [ :h1 " Reagent Form " ]]
[bind-fields form-template doc]
[ :label ( str @doc)]])))bind-fields功能接受可选事件。每当文档更新时,都会触发事件,并将按列出的顺序执行。每个事件都会看到其前身修改的文档。
事件必须采用3个参数,即id , path , value和document 。 id匹配字段的:id , path是文档中字段的路径, value表示以表单为单位的值,并且文档包含表单的状态。事件可以返回更新的文档或nil ,当返回nil ,则文档的状态未修改。
以下是一个事件的一个示例,用于计算:weight和:height密钥时: :bmi键的值:
( defn row [label input]
[ :div.row
[ :div.col-md-2 [ :label label]]
[ :div.col-md-5 input]])
( def form-template
[ :div
[ :h3 " BMI Calculator " ]
( row " Height " [ :input { :field :numeric :id :height }])
( row " Weight " [ :input { :field :numeric :id :weight }])
( row " BMI " [ :input { :field :numeric :id :bmi :disabled true }])])
[bind-fields
form-template
doc
( fn [id path value { :keys [weight height] :as doc}]
( when ( and ( some #{id} [ :height :weight ]) weight height)
( assoc-in doc [ :bmi ] ( / weight ( * height height)))))]您可以为bind-fields提供自定义的事件功能地图,以将试剂形式与re-frame之类的库一起使用。在这种情况下,试剂形式将不会持有您提供的任何内部状态和功能,将用于获取,保存和更新该字段的值。这是一个例子:
( ns foo.bar
( :require [re-frame.core :as re-frame]
[reagent-forms.core :refer [bind-fields]]))
; re-frame events
( re-frame/reg-event-db
:init
( fn [_ _]
{ :doc {}}))
( re-frame/reg-sub
:doc
( fn [db _]
( :doc db)))
( re-frame/reg-sub
:value
:<- [ :doc ]
( fn [doc [_ path]]
( get-in doc path)))
( re-frame/reg-event-db
:set-value
( fn [db [_ path value]]
( assoc-in db ( into [ :doc ] path) value)))
( re-frame/reg-event-db
:update-value
( fn [db [_ f path value]]
( update-in db ( into [ :doc ] path) f value)))
; Functions that will be called by each individual form field with an id and a value
( def events
{ :get ( fn [path] @( re-frame/subscribe [ :value path]))
:save! ( fn [path value] ( re-frame/dispatch [ :set-value path value]))
:update! ( fn [path save-fn value]
; save-fn should accept two arguments: old-value, new-value
( re-frame/dispatch [ :update-value save-fn path value]))
:doc ( fn [] @( re-frame/subscribe [ :doc ]))})
; bind-fields called with a form and a map of custom events
( defn foo
[]
[bind-fields
[ :div
[ :input { :field :text
:id :person.name.first
:valid? ( fn [doc]
( when ( = " Bob " ( -> doc :person :name :first ))
[ " error " ]))}]
[ :input { :field :text
:id :person.name.last }]]
events])可以通过在文档中提供ID来设置元素可见性,该文档将被视为真实值,或者函数:
( re-frame/reg-event-db
:toggle-foo
( fn [db _]
( update-in db [ :doc :foo ] not)))
( re-frame/reg-sub
:bar-visible?
( fn [db _]
( :bar db)))
( re-frame/reg-event-db
:toggle-bar
( fn [db _]
( update db :bar not)))
( def form
[ :div
[ :input { :field :text
:id :foo-input
:visible? :foo }]
[ :input { :field :text
:id :bar-input
:visible? ( fn [doc] @( re-frame/subscribe [ :bar-visible? ]))}]
( defn page
[]
[ :div
[bind-fields
[ :input { :field :text
:id :foo-input
:visible? :foo-input-visible? }]
event-fns]
[ :button
{ :on-click #( re-frame/dispatch [ :toggle-foo ])}
" toggle foo " ]
[ :button
{ :on-click #( re-frame/dispatch [ :toggle-bar ])}
" toggle bar " ]])如果您使用的是重新框架,则建议您使用重新框架事件来触发表单中字段的重新计算。例如,让我们看一下计算出的BMI字段:
( re-frame/reg-sub
:value
:<- [ :doc ]
( fn [doc [_ path]]
( get-in doc path)))
( defn bmi [{ :keys [weight height] :as doc}]
( assoc doc :bmi ( / weight ( * height height))))
( defmulti rule ( fn [_ path _] path))
( defmethod rule [ :height ] [doc path value]
( bmi doc))
( defmethod rule [ :weight ] [doc path value]
( bmi doc))
( defmethod rule :default [doc path value]
doc )
( re-frame/reg-event-db
:set-value
( fn [{ :keys [doc] :as db} [_ path value]]
( -> db
( assoc-in ( into [ :doc ] path) value)
( update :doc rule path value))))
( def events
{ :get ( fn [path] @( re-frame/subscribe [ :value path]))
:save! ( fn [path value] ( re-frame/dispatch [ :set-value path value]))
:doc ( fn [] @( re-frame/subscribe [ :doc ]))})
( defn row [label input]
[ :div
[ :div [ :label label]]
[ :div input]])
( def form-template
[ :div
[ :h3 " BMI Calculator " ]
( row " Height " [ :input { :field :numeric :id :height }])
( row " Weight " [ :input { :field :numeric :id :weight }])
( row " BMI " [ :label { :field :label :id :bmi }])])
( defn home-page []
[ :div
[ :h2 " BMI example " ]
[bind-fields form-template events]])当调用:set-value事件时,将触发规则多组的rule ,并且在更新高度或重量时,它将计算BMI。
可以通过实现reagent-forms.core/init-field MULTIMETHOD。该方法必须采用两个参数,其中第一个参数是字段组件,第二个参数是选项。
默认情况下,选项将包含get和save! , update!钥匙。 get关键点,该函数接受ID并返回与其关联的文档值。 save!功能接受ID和将与之关联的值。 update!功能接受ID,将处理更新的函数以及值。处理更新的功能将接收旧值和新值。
可以向字段提供适配器,以创建用于字段值的自定义存储格式。这些是一对通过键传递到字段的功能:in-fn和:out-fn 。 :in-fn修改存储的项目,以便该字段可以利用它,而以下内容:out-fn在存储之前修改了字段的输出。例如,为了将本机js/Date对象用作存储格式,可以因此初始化日期点:
[ :div { :field :datepicker :id :birthday :date-format " yyyy/mm/dd " :inline true
:in-fn #( when % { :year ( .getFullYear %) :month ( .getMonth %) :day ( .getDate %)})
:out-fn #( when % ( js/Date ( :year %) ( :month %) ( :day %)))}]适配器可以通过nulls,因此必须能够处理这些转移。
iOS上的Safari将有300ms的延迟:on-click事件,可以使用:touch-event键设置自定义触发事件。有关React中可用的事件列表,请参见此处。例如,如果我们想使用:on-touch-start而不是:on-click以触发事件,那么我们可以执行以下操作:
[ :input.form-control { :field :text :id :first-name :touch-event :on-touch-start }]请注意,您还必须设置cursor: pointer ,以便事件在iOS上工作。
React的磁带Ventplugin是创建响应事件的另一种选择,直到功能在React本身中可用为止。
该项目使用Doo进行测试。您必须安装一个DOO支持的环境之一,请参阅文档以获取详细信息。要运行测试,例如使用Phantom,请执行:
lein doo slimer test
版权所有©2018 Dmitri Sotnikov
根据Eclipse公共许可分发行1.0版或(根据您的选项)任何以后的版本。