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, shortcuts are and will be offered for various patterns, but they are just shortcut. For example, you may be using the bootstrap-3 shortcuts to succinctly generate a Boostrap form but there's always one or two fields that just need some special treatment. Stepping outside of the Bootstrap path and going custom with Free-form is not an afterthought but a well supported technique. For example, this won't be using an internal API that might change without warning, it's using the same API bootstrap-3 is using and using it directly is fine. In the future, we might want to provide some sort of pluggable system for different styles of templates.
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.
This library is far from complete. The focus so far was in establishing how it should work and not attempt to provide a complete form solution. For example, there's no support selects or text areas but adding them should be easy. This is likely to be fixed soon but pull requests are welcome. Another important shortcoming is the lack of tests. If you start using the library and find it's not flexible enough, please, get in touch. The goal is for it to be super flexible and useful in a wide range of situations.
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 functionget-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 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:
[:div.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 in this way:
[:div.form-horizontal {:free-form/options {:mode :bootstrap-3}}
[:free-form/field {:type :email
:key :email
:label "Email"}]]
The :free-form/options {:mode :bootstrap-3}
is what triggers Bootstrap 3 generation and Free-form will
automatically detect wether it's a standard, horizontal
or inline form.
- Started Bootstrap 3 support.
- Change API from
:free-form/field
to:free-form/input
. - Created example app to help test, exercise and develop the library: http://free-form-examples.carouselapps.com
- Fixed a bug when dealing with errors.
- Initial version extracted from Ninja Tools.
This library has been extracted from the project Ninja Tools.
Copyright © 2015 Carousel Apps, Ltd. All rights reserved.
Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.