A ClojureScript library to help building web forms with Reagent and optionally re-frame (others are welcome too). The guiding principles behind Free-form is that you are in control of both you data workflow as well as your markup. You can see the library in action in the Free-form Examples app.
Free-form doesn't force any markup on you and thus creating your own is a first-class approach. To avoid code duplication there's an extension system that allows you to write forms in a very succinct way. A Bootstrap 3 extension comes with Free form but adding more is not hard. One of the advantages of these mechanism is that when you have a couple of fields that behave differently and need their own markup, you can still use a first class API and enjoy the advantage of value handling, validation errors and everything Free form has to offer.
Free-form doesn't handle state for you. You need to decide how to handle state. Free-form comes with a re-frame module that helps you plug your form into the re-frame state. If you use the Reagent core you can handle state anyway you want. Other modules could be created to handle state in different ways, for example, one could be created to have a private ratom the way Reagent-forms do it.
Free-form supports but doesn't provide validation. It works very well, for example, with Validateur. Nothing special has been done for this, so it should be flexible for any library. If it isn't, please, submit a bug report.
The way this library works is that you write (or generate) the HTML template the way you normally do with Reagent, for example:
[:input {:type :email
:id :email
:placeholder "sam@example.com"}]
which then you pepper with special keywords to trigger the activation of inputs, labels, validation, etc. For example, to make this input to connect to the email we would change it to:
[:input {:free-form/input {:key :email}
:type :email
:id :email
:placeholder "sam@example.com"}]
Reagent-forms was a big inspiration but the way it handles state was not ideal in a re-frame scenario.
First, you have to include Free-form in your project:
To activate a form you call free-form.core/form
passing the set of values to display when the form is shown for
the first time, the set of errors to display, a callback function to receive changes to the state and the form itself.
For example:
[free-form.core/form {:email "pupeno@example.com"}
{:email ["Email addresses can't end in @example.com"]}
(fn [keys value] (println "Value for" keys "changed to" value))
[...]]
Notice that it's using square brackets because it's a Reagent component. You are likely to pass the contents of ratoms so that the form will be connected to live data, like:
[free-form.core/form @values @errors save-state
[...]]
The form is just your traditional Reagent template:
[free-form.core/form @values @errors save-state
[:label {:for :email} "Email"]
[:input.form-control {:free-form/input {:key :email}
:free-form/error-class {:key :text :error "error"}
:type :email
:id :email}]
[:div.errors {:free-form/error-message {:key :email}} [:p.error]]]
There are three special keywords added:
:free-form/input
marks the element as being an input and the passed key is to be used to connect to the value. As an alternative, you can pass a set of keys, as in: {:keys [:user :email]}
, as you do with the function get-in
.:free-form/error-class
will add a class if there's a validation error for the field. As with the previous one, :key
or :keys
marks the field, and :error
the class to be added in case of error.:free-form/error-message
adds error messages. If there are no error messages, the surrounding element, in this case :div.errors
will not be output at all. The field to be read from the map of errors is specified by :key
or :keys
. Lastly, the element inside this element will be used to output each of the error messages, so this might end up looking like: [:div.error [:p.error "Password is too short"] [:p.error "Password must contain a symbol"]]
When using Free-form with re-frame, the form is built in exactly the same way, but instead of having to code your own state management function, you can pass the name of the event to be triggered:
[free-form.re-frame/form @values @errors :update-state
[...]]
And the library will dispatch [:update-state keys new-value]
. If you need to pass extra arguments to the handler,
specify it as a vector.
[free-form.re-frame/form @values @errors [:update :user]
[...]]
If you need to generate more involved events to dispatch, you can pass a function that will get the keys and the new value and generate the event to be dispatched. For example:
[free-form.re-frame/form @values @errors (fn [keys new-value] [:update :user keys new-value])
[...]]
You can manually generate Bootstrap 3 forms by using code such as:
[free-form.core/form @values @errors save-state
[:form.form-horizontal
[:div.form-group {:free-form/error-class {:key :email :error "has-error"}}
[:label.col-sm-2.control-label {:for :email} "Email"]
[:div.col-sm-10 [:input.form-control {:free-form/input {:key :email}
:type :email
:id :email}]
[:div.text-danger {:free-form/error-message {:key :email}} [:p]]]]]]
but since that pattern is so common, it is now supported by an extension:
(ns whatever
(:require [free-form.core :as free-form]
free-form.bootstrap-3))
[free-form/form @values @errors save-state :bootstrap-3
[:form.form-horizontal
[:free-form/field {:type :email
:key :email
:label "Email"}]]]
You need to require free-form.bootstrap-3
for the extension to be available. The extra argument,
:bootstrap-3
is what triggers Bootstrap 3 generation and Free-form will automatically detect whether it's a
standard, horizontal
or inline form.
The debug extension just prints the form before and after any other processing happens.
(ns whatever
(:require [free-form.core :as free-form]
free-form.debug))
There's a fourth optional argument to specify one or more extensions to be applied to the form. For example, with only one extension called bootstrap-3:
[free-form.core/form @values @errors save-state :bootstrap-3
[...]]
or with multiple:
[free-form.core/form @values @errors save-state [:bootstrap-3 :debug]
[...]]
Extensions essentially wrap the form and thus, order is important and they can be provided more than once. For example:
[free-form.core/form @values @errors save-state [:debug :bootstrap-3 :debug]
[...]]
would help you see what the Bootstrap 3 extension is doing.
Extensions are implemented by adding a method to the multi-method free-form.extension/extension. This method will get the name of the extension and the function that process the form. This function gets the unprocessed html markup and returns the processed html structure. The extension should return a function that does essentially the same plus whatever the extension wants to do. This is a system similar to middlewares found in many libraries. For example:
(defmethod free-form.extension/extension :extension-name [_extension-name inner-fn]
(fn [html]
(do-something-else (inner-fn (do-something html)))))
do-something would pre-process the raw structure and do-something-else would post-process the structure after all inner extensions and the main inner function have been called.
See the debug and the Bootstrap 3 extensions for examples.
This is a completely incomplete list of people/projects using Free-form:
:free-form/field
to :free-form/input
.Copyright © 2015-2017 José Pablo Fernández Silva
Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.
Can you improve this documentation? These fine people already did:
J. Pablo Fernández, J. Pablo Fernández, Juan José Conti & RussellEdit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close