一個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版或(根據您的選項)任何以後的版本。