A Clojurescript library that lets you build beautiful data-binding forms with Om, Reagent and Rum.
You can write code that is fully portable between Reagent, Om and Rum making it easier to reuse code and giving you a clear migration path.
To help you quickly create beautiful forms without messing with CSS, the generated markup is compatible with Bootstrap 3 CSS and Font Awesome. For quick results simply include Bootstrap and Font Awesome CSS.
If you think something useful is missing though, please let me know.
A good place to see the available controls: demo.
Add om-reforms
to :dependencies
in project.clj:
Minimal requires (including sablono to render the forms):
(ns hello-world.core
(:require [reforms.om :include-macros true :as f]
[om.core :as om]
[sablono.core :include-macros true :as sablono]))
Here's how you create an Om component with a form with just one text field and a button:
(defn simple-view
[data _owner]
(om/component
(sablono/html
(f/form
(f/text "Your name" data [:name])
(f/form-buttons
(f/button "Submit" #(js/alert (:name @data))))))))
You render it with om/build
just like any other component. See https://github.com/omcljs/om for more details.
Note that labels are optional, you can render controls without labels, for instance:
(f/text data [:name] :placeholder "Enter your name here")
Add reagent-reforms
to :dependencies
in project.clj:
(ns hello-world.core
(:require [reforms.reagent :include-macros true :as f]
[reagent.core :refer [atom render-component]))
Here's how you create a Reagent component with a form with just one text field and a button:
(defn simple-view
[data]
(f/form
(f/text "Your name" data [:name])
(f/form-buttons
(f/button "Submit" #(js/alert (:name @data))))))
You render it just like any other component by either mounting it using render-component
or inside another component using the [simple-view some-data]
syntax. See https://github.com/reagent-project/reagent for more details.
Note that labels are optional, you can render controls without labels, for instance:
(f/text data [:name] :placeholder "Enter your name here")
Add rum-reforms
to :dependencies
in project.clj:
(ns hello-world.core
(:require [reforms.rum :include-macros true :as f]
[rum.core :include-macros true :as rum])
Here's how you create a Rum component with a form with just one text field and a button:
(rum/defc simple-view < rum/cursored rum/cursored-watch [data horizontal-orientation]
[data]
(f/form
(f/text "Your name" data [:name])
(f/form-buttons
(f/button "Submit" #(js/alert (:name @data))))))
You render it just like any other component by either mounting it using rum-mount
or inside another component. See https://github.com/tonsky/rum for more details.
Note that labels are optional, you can render controls without labels, for instance:
(f/text data [:name] :placeholder "Enter your name here")
The library does not use Bootstrap JavaScript so just link to bootstrap css from your html page, e.g.:
<link href="https://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet"/>
Optionally, to use Font Awesome icons to use features such as progress spinner, warning icons etc., link to it as well:
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet">
The tutorial shows library-agnostic code. For code specific to Om or React, see "Getting started with ..." above or the examples.
Here's how you create a form with just one text field and a button:
(f/form
(f/text "Your name" data [:name])
(f/form-buttons
(f/button "Submit" #(js/alert (:name @data)))))
Note that form
returns a Hiccup-like data structure. The example below, though a bit simplified and scrubbed for clarity, should give you an idea:
[:form [:div {:class "form-group"
:key "data-name"}
[:label {:for "data-name"
:class "control-label "} "Your name"]
[:input {:value "My name"
:type "text"
:class "form-control"
:id "data-name"
:placeholder "Type your name here"}]]
[:div.form-group.form-buttons
[:button {:type "button"
:class "btn btn-primary"
:onClick #(js/alert (:name @data))} "Submit"]]]
The controls bind directly to data (Om cursors or Reagent ratoms). For example, as the user types text into the text box below, data
is automatically updated:
(f/text "Your name" data [:name])
(prn @data) ;; => {:name "John Wayne}
You can add a placeholder shown when the text box is empty using a :placeholder
option:
(f/text "Your name" data [:name] :placeholder "Enter your name here")
To change the orientation use with-options
:
(f/with-options {:form {:horizontal true}}
(f/form
(f/text "Your name" data [:name] :placeholder "Enter your name here")
(f/form-buttons
(f/button "Submit" #(js/alert (:name @data))))))
To wrap the form in a panel use panel
:
(f/panel
"Hello, world"
(f/form
(f/text "Your name" data [:name] :placeholder "Enter your name here")
(f/form-buttons
(f/button "Submit" #(js/alert (:name @data))))))
Finally, let's take make the button clearly a primary one and add a cancel button and, just for the fun of it, a checkbox that toggles the orientation:
(f/form
(f/text "Your name" data [:name] :placeholder "Enter your name here")
(f/form-buttons
(f/button-primary "Submit" #(js/alert (:name @data)))
(f/button-default "Cancel" #(js/alert "Cancel!")))
(f/checkbox "Horizontal form" data [:orientation-horizontal]))
Click!
The complete example: Om (demo) Reagent (demo).
For the list of available controls, see the API Reference.
The library supports client-side data validation.
To use validators, require
reforms.validation
, use form and form field helpers from this namespace instead of reforms.core
and use validate!
:
(ns my-validation-example
(:require ...
[reforms.validation :include-macros true :as v]))
Apart from form
, the helpers have an identical interface to ones in reforms.core
.
(v/form ;; 1
ui-state ;; 2
(v/text "Login" data [:login]) ;; 3
(v/password "Password" data [:password1])
(v/password "Confirm password" data [:password2])
(f/form-buttons
(f/button-primary "Sign up" #(sign-up! data ui-state)))) ;; 4
reforms.validation/form
. Note that it takes an extra argument (2).data
to bind the form fields to and ui-state
to store validation results in. There's no technical reason we cannot use data
for this but separating this makes it cleaner.reforms.validation
.Here's the sign up function. It shows an alert if data validates:
(defn sign-up!
[data ui-state]
(when (v/validate! ;; 1
data ;; 2
ui-state ;; 3
(v/present [:login] "Enter login name") ;; 4
(v/equal [:password1] [:password2] "Passwords do not match")
(v/present [:password1] "Choose password")
(v/present [:password2] "Re-enter password"))
(js/alert "Signed up!"))
validate!
returns a truthy value if data is valid.Here's what happens after you click "Sign up" while all fields are empty:
To satisfy your curiosity, here are the contents of ui-state
:
{:validation-errors [{:korks #{[:login]}, :error-message "Enter login name"}
{:korks #{[:password1]}, :error-message "Choose password"}
{:korks #{[:password2]}, :error-message "Re-enter password"}]}
A slightly richer example: Om (demo) Reagent (demo).
For the list of available validators, see the API Reference.
A validator is a function that returns a lambda that takes some data and returns nil
or a validation error. Let's create a custom validation that checks if data is a positive number:
(defn positive-number?
[s]
(pos? (js/parseInt s)))
(defn positive-number
[korks error-message] ;; 1
(fn [cursor] ;; 2
(when-not (positive-number? (get-in cursor korks)) ;; 3
(v/validation-error [korks] error-message)))) ;; 4
korks
pointing to data we want to validate and the error message. This is a typical pattern.cursor
.While we're at it, we could make it more readable with the built-in is-true
validator:
(defn positive-number
[korks error-message]
(v/is-true korks positive-number? error-message))
Either way, you can use your brand new validator like a pro:
(validate!
data
ui-state
(positive-number [:age] "Age must be a positive number"))
Validation errors may be forced which comes useful when using external APIs etc. Observe:
(v/validate!
customer
ui-state
(v/force-error [:server-error] "An error has occurred"))
You'd normally call it from an asynchronous error handler, go block etc.
You can either have a form field show the error if it makes sense by passing its korks to force-error
or use the error-alert
helper to render the error:
(v/error-alert [:server-error])
Note that error-alert
can render any number of custom errors like so:
(v/error-alert [:auth-error] [:twitter-error])
Starting with version 0.4.0 Reforms support HTML tables with optional row selection fully stylable using CSS and compatible with Bootstrap table classes (if you use Bootstrap in the first place).
For live example see this demo (source).
This is how you create a simple table, just provide a vector with map per each row:
(t/table [{:name "Tom"} {:name "Jerry"} {:name "Mickey"} {:name "Minnie"}])
Here we create just one column.
It's usually a good idea to give columns human-friendly titles:
(t/table [{:name "Tom"} {:name "Jerry"} {:name "Mickey"} {:name "Minnie"}]
:columns {:name "Hero name"})
As with all controls, you can specify optional attributes; they will be applied to the