auto-generated docs: https://byu-odh.github.io/reformation/
Installing in Leiningen:
[edu.byu.odh/reformation "25"]
Reformation is a library for building web forms. More specifically, the library outputs Clojure data structures that can be easily rendered by Reagent.
Forms are specified by a simple, hiccup-like language. A form is described by a vector, alternating between keywords and attribute maps. Each pair represents a form element; the element is named by the keyword, and described by the map of attributes.
`render-application` is the main call you’ll be making. It takes two arguments: a vector describing the form, and a place to store the data(see “Data Storage” below).
(ns example
(:require [reformation.core :as rfc]))
(def example-atom (atom nil))
(def easy-form [:example-element {:type :text
:label "Enter some text here"}
:example-element2 {:type :checkbox
:label "Is it true?"}])
(defn form-component []
[:div (rfc/render-application easy-form example-atom)])
![What it looks like](docs/simpleform.png)
Where does the data live?
render-application
can take an atom as a second argument and keep user input there. The atom should either contain nil
or a map.
If you have more specific needs (a front-end db, etc), instead of an atom, you can pass render-application
a map containing read and write functions for Reformation to use, under the keys :READ
and :WRITE
. The function under :READ
should take one argument: a vector of keys representing a path as in get-in
. The function under :UPDATE
should take [path new-val-f]
, where new-val-f
is a zero-argument function that returns the new value.
(rfc/render-application test-form {:READ (partial get-in @my-atom)
:UPDATE (partial swap! my-atom update-in)})
Reformation includes two constructs we’ve found to be particularly useful: “multitables” and “toggleboxes.”
Multitables are for when you’re not sure how many items the user may need/want to include—when they might list multiple expenses, reasons, referrals, etc.
Toggleboxes are upgraded checkboxes. They contain their own content, which is only shown if the box is checked.
(ns example
(:require [reformation.core :as rfc]))
(def test-form [:mytext {:type :text
:label "My text"
:name-separator "_"}
:mytextarea {:type :textarea
:label "My textarea"
:rows 5}
:mymultitable {:type :multi-table
:label "My multitable"
:id :mymulti
:required? true
:min-rows 3
:subtext "Indicate any expenses involved in carrying out your research, including a reason for each expense."
:value-path [:my-multitable]
:sum-field :amount
:columns [{:key :item
:title "Item"}
{:key :amount
:title "Amount"
:input-type "number"}
{:key :purpose
:title "Purpose"
:input-type "textarea"}]}
:mytoggle {:type :togglebox
:label "My togglebox"
:content [:test {:type :text :label "My toggled "}]}
:mycheckbox {:type :checkbox :label "My checkbox"}
:myfileupload {:type :file
:label "My file"
:submit-text "Click or Drop a File Here"
:error-text "Maybe We had an error?"
:submit-fn #(js/alert "Trying to submit:")
:save-fn #(reset! FILE %)
:allowed-extensions-f #{"txt"}
:style-classes {:drag-over "dragover"
:inactive "undragged"
:have-file "have-file"}}])
Indicates which element to create. Can be any of: :select
, :radio
, :multi-table
, :textarea
, :togglebox
, :checkbox
, :file
, :hidden
Explanatory or parenthetical text, appearing under :label
. Adjust style by adding rules to p.help
Should be a predicate function that takes a value and determines whether it is a valid input for that form element
If the input doesn’t satisfy the validation function, this string will appear explaining why
A boolean flag indicating whether the element is required for form submission. Not applicable to togglebox or =multi-table
If you wish to set a default value, put it here
A binary flag for whether the element will be disabled
the id
of the resulting element
Is set as :class
of the resulting element
:options
a sequence of options from which the user can select one. Each option has :content
and :value
attributes; you can provide a map with the appropriate keys or a string (in which case it will be used in both attributes). If one key is missing, the value of the other will be used.
:options
a sequence of options from which the user can select one. Each option has :content
and :value
attributes; you can provide a map with the appropriate keys or a string (in which case it will be used in both attributes). If one key is missing, the value of the other will be used.
:placeholder
as the HTML attribute.
:value
as the html attribute
:char-count
a map with two keys, :limit
and :enforce?
. :limit
is a character count limit and should be an integer, :enforce?
is a boolean.
a checkbox.
:submit-text
Text instructing the user how to submit a file. Defaults to “Click or Drop a File Here”
[{:keys [ submit-text submit-fn error-text submit-button ] :or {submit-text “Click or Drop a File Here”} {:keys [drag-over inactive have-file] :or {drag-over “dragover” inactive “undragged” have-file “have-file”}} :style-classes :as opt-map}]
:content
The contents of a togglebox. Should be in the same form as a form description vector, i.e. a vector alternating between keys and element-description maps.
override-inline? not sure
open-height —dictates height when open, is passed directly to :height
in CSS. Should be a string value
An expandable table. A common task for forms is a list of an indefinite number of elements—expenses, group members, prior positions, etc.
:input-type
The input element. All elements used outside of multitable (except :file
, :togglebox
, and
hidden
Generates an goog autocomplete (no external dependencies required), which is given a reference subscription to the data it will include and supplemental information. Of course this subscription can reference an atom being populated by data from a remote call.
TO BE USABLE THE AUTOCOMPLETE MUST BE STYLED. Working styling, selecting on the goog-created classes, is available in reformation at reformation.styles.main/autocomplete
.
Options:
:data-subscription
:input-id
:separators
:literals
:multi?
:throttle-time
:fuzzy?
:display-name
:val-key
Versions 15+ now allow inclusion of a :DICTIONARY
to perform a replacement of matching namespaced (any namespace) keywords within any value position of your control map. This is designed to allow you to include values in your map that will be provided by a function (for example an api call) while still allowing it to be raw data that can be included, for instance, in a database or a plain data output (the schema just has some keywords).
Example (which also demonstrates optional use of re-frame):
(def DICTIONARY {:example/input-kw {:type :text
:label "default kw-mapped text"
:default-value "something good"
:disabled true
:style-classes "I-like-red"}
:example/default-scalar "Just a value from a keyword"
:example/default-options ["option-1" "option-2" "option-3"]})
(def test-form-with-map [:example_element2 {:type :text
:invalid-feedback "Just type @..."
:label "Enter the @ symbol"
:required true
:id "example2"}
:mydefault-text :example/input-kw
:myselect {:label "A select"
:type :select
:options :example/default-options}
:mytext {:type :text
:label :example/default-scalar}])
(def control-map {:DICTIONARY DICTIONARY
:READ
(fn [kv]
@(reframe/subscribe [:read-form-item kv]))
#_ (partial get-in @my-atom)
:UPDATE
(fn [kv update-function]
;; dispatch-sync is required here, because the defer involved in plain reframe/dispatch causes the synthetic event to be released and the fn breaks.
(reframe/dispatch-sync [:update-form kv update-function]))})
(defn generate-form []
(let [form-id "needs-validation"]
[:div.submission-form
[:form.form-control {:id form-id}
(into [:div.form-contents]
(reformation.core/render-application test-form-with-map control-map))]]))
(rfc/check-form-validation)
A predicate function that will check the validity of every element in your form. Returns false if any do not pass the validation requirements. Currently supports the use of one form per page.
(ns example
(:require [reformation.core :as rfc]))
(def f1 #(if (> (count %) 5)
true
nil))
(def text-form [:example-element {:type :text
:validation-function f1
:invalid-feedback "Needs more than 5 characters..."
:label "Enter more than 5 characters"
:id "example1"})
(defn save-button
[]
[:a.button {:id "submit"
:title "Submit form"
:on-click #(if (rfc/check-form-validation)
(js/alert "Passed")
(js/alert "Failed"))
:href nil} "Submit"]])
None. Just use this library.
Copyright © 2023 Tory S. Anderson, BYU Office of Digital Humanites