Skip to content

BYU-ODH/reformation

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Reformation

auto-generated docs: https://byu-odh.github.io/reformation/

Installing in Leiningen: [edu.byu.odh/reformation "25"]

Overview

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

A Small Reformation Example

(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)

Data Storage

Where does the data live?

An Atom

render-application can take an atom as a second argument and keep user input there. The atom should either contain nil or a map.

Custom Storage

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)})

Multitables and Toggleboxes

Reformation includes two constructs we’ve found to be particularly useful: “multitables” and “toggleboxes.”

Multitables

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

Toggleboxes are upgraded checkboxes. They contain their own content, which is only shown if the box is checked.

A Bigger Form
(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"}}])

Element and Attribute Reference

universal keys

:type

Indicates which element to create. Can be any of: :select, :radio, :multi-table, :textarea, :togglebox, :checkbox, :file, :hidden

:subtext

Explanatory or parenthetical text, appearing under :label. Adjust style by adding rules to p.help

:validation-function

Should be a predicate function that takes a value and determines whether it is a valid input for that form element

:invalid-feedback

If the input doesn’t satisfy the validation function, this string will appear explaining why

:required?

A boolean flag indicating whether the element is required for form submission. Not applicable to togglebox or =multi-table

:default-value

If you wish to set a default value, put it here

:disabled

A binary flag for whether the element will be disabled

:id

the id of the resulting element

:style-classes

Is set as :class of the resulting element

Elements

:select

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

:radio

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

:textarea

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

:checkbox

a checkbox.

:file

:submit-text Text instructing the user how to submit a file. Defaults to “Click or Drop a File Here”

:submit-button a

:submit-fn

:save-fn should be a fn of one argument, the file

:allowed-extensions-f a set of strings, each representing a file extension, e.g. #{“txt” “doc”}

[{: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}]

:togglebox

: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

:multi-table

An expandable table. A common task for forms is a list of an indefinite number of elements—expenses, group members, prior positions, etc.

:min-rows The minimum acceptable number of rows. Not required.

:sum-field the name of the field to sum

:columns a vector of maps (?). Each column is represented by a map with keys:

:key the key attribute React uses to order the columns. If none is provided, :title will be used
:title Title of the column
:input-type The input element. All elements used outside of multitable (except :file, :togglebox, and
:column-class is added to the :class of the column
:input-class is added to the :class of the input elements
:disabled Set this to true to disable the column. Defaults to false
:placeholder For text inputs, the placeholder attribute
:default-value For text inputs, the initial value

hidden

:autocomplete

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

Using a :DICTIONARY

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

“required” on regular input

Format fn for date fields

Default values for select boxes

Validation

(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"]])

Prerequisites

None. Just use this library.

License

Copyright © 2023 Tory S. Anderson, BYU Office of Digital Humanites

About

Reagent form solution

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published