rsslldnphy/free-form

Library for building forms with Reagent or Re-frame.


License
EPL-1.0

Documentation

Free-form

Code at GitHub Clojars Build Status

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.

Current state

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. An 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.

Usage

First, you have to include Free-form in your project:

Clojars 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"]]

re-frame

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])
 [...]]

Bootstrap 3

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.

Changelog

v0.4.2 - 2016-11-12

  • Make all inputs controlled so changes can come from within our from outside.

v0.4.1 - 2016-10-25

  • Added the sources directory to the project.clj so that the library is correctly packaged.

v0.4.0 - 2016-10-24

  • Tested Free-form with re-frame 0.8.0 and Reagent 0.6.0.
  • Allow marking a field as invalid when another one is invalid with :extra-keys.
  • Added error messages to Bootstrap inline and horizontal forms.
  • Correctly specify dependencies on Clojure and ClojureScript to avoid fixing to a single version.
  • When there's no validation error, don't return the form template (invalid HTML), return nil instead.
  • After a form with Bootstrap has been processed, remove the option to trigger that processing (it's invalid HTML).
  • Show a JavaScript console error if there are any Free-form leftovers after all processing is done.

v0.3.0 - 2016-08-16

  • Changed namespace from com.carouselapps to com.pupeno
  • Implemented selects.
  • Implemented text areas.

v0.2.1 - 2016-08-22

  • Changed the metadata of the library to point to the new namespace.

v0.2.0 - 2015-12-14

  • 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.pupeno.com

v0.1.1 - 2015-10-15

  • Fixed a bug when dealing with errors.

v0.1.0 - 2015-10-11

License

Copyright © 2015, 2016 José Pablo Fernández Silva.

Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.