{"id":13801132,"url":"https://github.com/reagent-project/reagent-forms","last_synced_at":"2025-07-28T12:44:13.415Z","repository":{"id":19592356,"uuid":"22842787","full_name":"reagent-project/reagent-forms","owner":"reagent-project","description":"Bootstrap form components for Reagent","archived":false,"fork":false,"pushed_at":"2020-04-27T13:32:54.000Z","size":373,"stargazers_count":339,"open_issues_count":9,"forks_count":78,"subscribers_count":16,"default_branch":"master","last_synced_at":"2025-07-09T20:37:50.863Z","etag":null,"topics":["clojurescript","forms","reagent","web"],"latest_commit_sha":null,"homepage":null,"language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/reagent-project.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-08-11T14:53:07.000Z","updated_at":"2025-06-05T18:09:49.000Z","dependencies_parsed_at":"2022-07-23T13:02:15.469Z","dependency_job_id":null,"html_url":"https://github.com/reagent-project/reagent-forms","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/reagent-project/reagent-forms","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reagent-project%2Freagent-forms","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reagent-project%2Freagent-forms/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reagent-project%2Freagent-forms/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reagent-project%2Freagent-forms/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/reagent-project","download_url":"https://codeload.github.com/reagent-project/reagent-forms/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reagent-project%2Freagent-forms/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267520063,"owners_count":24100815,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-28T02:00:09.689Z","response_time":68,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clojurescript","forms","reagent","web"],"created_at":"2024-08-04T00:01:19.896Z","updated_at":"2025-07-28T12:44:13.392Z","avatar_url":"https://github.com/reagent-project.png","language":"HTML","readme":"# reagent-forms\n\nA ClojureScript library to provide form data bindings for [Reagent](http://holmsand.github.io/reagent/), see [here](http://yogthos.github.io/reagent-forms-example.html) for a live demo.\n\n## Table of contents\n\n- [Install](#install)\n- [Usage](#usage)\n    - [:input](#input)\n    - [:typeahead](#typeahead)\n    - [:checkbox](#checkbox)\n    - [:range](#range)\n    - [:radio](#radio)\n    - [:file](#file)\n    - [:files](#files)\n  - [Lists](#lists)\n    - [:list](#list)\n    - [:single-select](#single-select)\n    - [:multi-select](#multi-select)\n    - [:label](#label)\n    - [:alert](#alert)\n    - [:datepicker](#datepicker)\n    - [:container](#container)\n  - [Validation](#validation)\n  - [Setting component visibility](#setting-component-visibility)\n  - [Updating attributes](#updating-attributes)\n- [Binding the form to a document](#binding-the-form-to-a-document)\n- [Adding events](#adding-events)\n- [Using with re-frame](#using-with-re-frame)\n- [Adding custom fields](#adding-custom-fields)\n- [Using adapters](#using-adapters)\n- [Mobile Gotchas](#mobile-gotchas)\n- [Testing](#testing)\n- [License](#license)\n\n## Install\n\n[![Clojars Project](https://img.shields.io/clojars/v/reagent-forms.svg)](https://clojars.org/reagent-forms)\n\n## Usage\n\nThe library uses a Reagent atom as the document store. The components are bound to the document using the `:field` attribute. This key will be used to decide how the specific type of component should be bound. The component must also provide a unique `:id` attribute that is used to correlate it to the document. While the library is geared towards usage with Twitter Bootstrap, it is fairly agnostic about the types of components that you create.\n\nThe `:id` can be a keyword, e.g: `{:id :foo}`, or a keywordized path `{:id :foo.bar}` that will map to `{:foo {:bar \"value\"}}`. Alternatively, you can specify a vector path explicitly `[:foo 0 :bar]`.\n\nBy default the component value is that of the document field, however all components support an `:in-fn` and `:out-fn` function attributes.\n`:in-fn` accepts the current document value and returns what is to be displayed in the component. `:out-fn` accepts the component value\nand returns what is to be stored in the document.\n\nThe following types of fields are supported out of the box:\n\n#### :input\n\nAn input field can be of type `:text`, `:numeric`, `:range`, `:password`, `:email`, and `:textarea`. The inputs behave just like regular HTML inputs and update the document state when the `:on-change` event is triggered.\n\n```clojure\n[:input.form-control {:field :text :id :first-name}]\n[:input.form-control {:field :numeric :id :age}]\n```\n\nThe input fields can have an optional `:fmt` attribute that can provide a format string for the value:\n\n```clojure\n[:input.form-control\n  {:field :numeric :fmt \"%.2f\" :id :bmi :disabled true}]\n```\n\nNumeric inputs support attributes for the HTML 5 number input:\n\n```clojure\n[:input\n {:field     :numeric\n  :id        :volume\n  :fmt       \"%.2f\"\n  :step      \"0.1\"\n  :min       0\n  :max       10}]\n```\n\n#### :typeahead\n\nThe typeahead field uses a `:data-source` key bound to a function that takes the current input and returns a list of matching results. The control uses an input element to handle user input and renders the list of choices as an  unordered list element containing one or more list item elements. Users may specify the css classes used to render each of these elements using the keys :input-class, :list-class and :item-class. Users may additionally specify a css class to handle highlighting of the current selection with the :highlight-class key. Reference css classes are included in the resources/public/css/reagent-forms.css file.\n\n```clojure\n(defn friend-source [text]\n  (filter\n    #(-\u003e % (.toLowerCase %) (.indexOf text) (\u003e -1))\n    [\"Alice\" \"Alan\" \"Bob\" \"Beth\" \"Jim\" \"Jane\" \"Kim\" \"Rob\" \"Zoe\"]))\n\n[:div {:field :typeahead\n       :id :ta\n       :input-placeholder \"pick a friend\"\n       :data-source friend-source\n       :input-class \"form-control\"\n       :list-class \"typeahead-list\"\n       :item-class \"typeahead-item\"\n       :highlight-class \"highlighted\"}]\n```\n\nThe typeahead field supports both mouse and keyboard selection.\n\n##### Different display and value\n\nYou can make the input's displayed value be different to the value stored in the document. You need to specify `:out-fn`, a `:result-fn` and\noptionally `:in-fn`. The `:data-source` needs to return a vector `[display-value stored-value]`.\n\n```clojure\n(defn people-source [people]\n  (fn [text]\n    (-\u003e\u003e people\n         (filter #(-\u003e (:name %)\n                      (.toLowerCase)\n                      (.indexOf text)\n                      (\u003e -1)))\n         (mapv #(vector (:name %) (:num %))))))\n\n[:div {:field :typeahead\n       :data-source (people-source people)\n       :in-fn (fn [num]\n                [(:name (first (filter #(= num (:num %)) people))) num])\n       :out-fn (fn [[name num]] num)\n       :result-fn (fn [[name num]] name)\n       :id :author.num}]]]\n```\n\n##### Pop down the list\n\nIf `:data-source` responds with the full option list when passed the keyword `:all` then the down-arrow key will show the list.\n\n##### Selection list from Ajax\n\nThe `:selections` attribute can be specified to pass an atom used to hold the selections. This gives the option to fetch the\nlist using typeahead text - if an ajax response handler sets the atom the list will pop down.\n\n##### Display selection on pop-down\n\nIf supplied, the `:get-index` function will ensure the selected item is highlighted when the list is popped down.\n\nA full example is available in the source code for the demonstration page.\n\n#### :checkbox\n\nThe checkbox field creates a checkbox element:\n\n```clojure\n[:div.row\n  [:div.col-md-2 \"does data binding make you happy?\"]\n  [:div.col-md-5\n   [:input.form-control {:field :checkbox :id :happy-bindings}]]]\n```\n\nThe checkbox accepts an optional `:checked` attribute. When set the\ncheckbox will be selected and the document path pointed to by the `:id`\nkey will be set to `true`.\n\n```clojure\n[:div.row\n  [:div.col-md-2 \"does data binding make you happy?\"]\n  [:div.col-md-5\n   [:input.form-control {:field :checkbox :id :happy-bindings :checked true}]]]\n```\n\n#### :range\n\nRange control uses the `:min` and `:max` keys to create an HTML range input:\n\n```clojure\n[:input.form-control\n {:field :range :min 10 :max 100 :id :some-range}]\n```\n\n#### :radio\n\nRadio buttons do not use the `:id` key since it must be unique and are instead grouped using the `:name` attribute. The `:value` attribute is used to indicate the value that is saved to the document:\n\n```clojure\n[:input {:field :radio :value :a :name :radioselection}]\n[:input {:field :radio :value :b :name :radioselection}]\n[:input {:field :radio :value :c :name :radioselection}]\n```\n\nThe radio button accepts an optional `:checked` attribute. When set the\ncheckbox will be selected and the document path pointed to by the `:name` key\nwill be set to `true`.\n\n```clojure\n[:input {:field :radio :value :a :name :radioselection}]\n[:input {:field :radio :value :b :name :radioselection :checked true}]\n[:input {:field :radio :value :c :name :radioselection}]\n```\n\n#### :file\n\nThe file field binds the `File` object of an `\u003cinput type=\"file\"/\u003e`.\n\n```clojure\n[:input {:field :file :type :file}]\n```\n\n#### :files\n\nSame as file, except it works with `\u003cinput type=\"file\" multiple/\u003e` and binds the entire `FileList` object.\n\n```clojure\n[:input {:field :files :type :file :multiple true}]\n```\n\n### Lists\n\nList fields contain child elements whose values are populated in the document when they are selected. The child elements must each have a `:key` attribute pointing to the value that will be saved in the document. The value of the element must be a keyword.\n\nThe elements can have an optional `:visible?` keyword that points to a predicate function. The function should accept the document and return a boolean value indicatiing whether the field should be shown.\n\n#### :list\n\nThe `:list` field is used for creating HTML `select` elements containing `option` child elements:\n\n```clojure\n[:select.form-control {:field :list :id :many-options}\n  [:option {:key :foo} \"foo\"]\n  [:option {:key :bar} \"bar\"]\n  [:option {:key :baz} \"baz\"]]\n\n(def months\n  [\"January\" \"February\" \"March\" \"April\" \"May\" \"June\"\n   \"July\" \"August\" \"September\" \"October\" \"November\" \"December\"])\n\n[:select {:field :list :id :dob.day}\n      (for [i (range 1 32)]\n        [:option\n         {:key (keyword (str i))\n          :visible? #(let [month (get-in % [:dob :month])]\n                       (cond\n                        (\u003c i 29) true\n                        (\u003c i 31) (not= month :February)\n                        (= i 31) (some #{month} [:January :March :May :July :August :October :December])\n                        :else false))}\n          i])]\n[:select {:field :list :id :dob.month}\n  (for [month months]\n    [:option {:key (keyword month)} month])]\n[:select {:field :list :id :dob.year}\n  (for [i (range 1950 (inc (.getFullYear (js/Date.))))]\n    [:option {:key (keyword (str i))} i])]\n```\n\n\n#### :single-select\n\nThe single-select field behaves like the list, but supports different types of elements and allows the fields to be deselected:\n\n```clojure\n[:h3 \"single-select buttons\"]\n[:div.btn-group {:field :single-select :id :unique-position}\n  [:button.btn.btn-default {:key :left} \"Left\"]\n  [:button.btn.btn-default {:key :middle} \"Middle\"]\n  [:button.btn.btn-default {:key :right} \"Right\"]]\n\n[:h3 \"single-select list\"]\n[:ul.list-group {:field :single-select :id :pick-one}\n  [:li.list-group-item {:key :foo} \"foo\"]\n  [:li.list-group-item {:key :bar} \"bar\"]\n  [:li.list-group-item {:key :baz} \"baz\"]]\n```\n\n#### :multi-select\n\nThe multi-select field allows multiple values to be selected and set in the document:\n\n```clojure\n[:h3 \"multi-select list\"]\n[:div.btn-group {:field :multi-select :id :position}\n  [:button.btn.btn-default {:key :left} \"Left\"]\n  [:button.btn.btn-default {:key :middle} \"Middle\"]\n  [:button.btn.btn-default {:key :right} \"Right\"]]\n```\n\n#### :label\n\nLabels can be associated with a key in the document using the `:id` attribute and will display the value at that key. The lables can have an optional `:preamble` and `:postamble` keys with the text that will be rendered before and after the value respectively. The value can also be interpreted using a formatter function assigned to the `:fmt` key. The `:placeholder` key can be used to provide text that will be displayed in absence of a value:\n\n```clojure\n[:label {:field :label :id :volume}]\n[:label {:field :label :preamble \"the value is: \" :id :volume}]\n[:label {:field :label :preamble \"the value is: \" :postamble \"ml\" :id :volume}]\n[:label {:field :label :preamble \"the value is: \" :postamble \"ml\" :placeholder \"N/A\" :id :volume}]\n[:label {:field :label :preamble \"the value is: \" :id :volume :fmt (fn [v] (if v (str v \"ml\") \"unknown\")}]\n```\n\n#### :alert\n\nAlerts are bound to an id of a field that triggers the alert and can have an optional `:event` key. The event key should point to a function that returns a boolean value.\n\nAn optional `:closeable? true/false` can be provided to control if a close button should be rendered (defaults to true).\n\nWhen an event is supplied then the body of the alert is rendered whenever the event returns true:\n\n```clojure\n[:input {:field :text :id :first-name}]\n[:div.alert.alert-success {:field :alert :id :last-name :event empty?} \"first name is empty!\"]\n```\n\nWhen no event is supplied, then the alert is shown whenever the value at the id is not empty and displays the value:\n\n```clojure\n(def doc (atom {}))\n\n;;define an alert that watches the `:errors.first-name` key for errors\n[:div.alert.alert-danger {:field :alert :id :errors.first-name}]\n\n;;trigger the alert by setting the error key\n[:button.btn.btn-default\n  {:on-click\n    #(if (empty? (:first-name @doc))\n      (swap! doc assoc-in [:errors :first-name] \"first name is empty!\"))}\n  \"save\"]\n```\n\n#### :datepicker\n\n```clojure\n[:div {:field :datepicker :id :birthday :date-format \"yyyy/mm/dd\" :inline true}]\n```\nThe date is stored in the document using the following format:\n\n```clojure\n{:year 2014 :month 11 :day 24}\n```\n\nThe datepicker can also take an optional `:auto-close?` key to indicate that it should be closed when the day is clicked. This defaults to `false`.\n\n\nThe date format can be set using the `:date-format` key:\n\n```Clojure\n{:field :datepicker :id :date :date-format \"yyyy/mm/dd\"}\n```\n\nThe `:date-format` can also point to a function that returns the formatted date:\n```Clojure\n{:field :datepicker\n :id :date\n :date-format (fn [{:keys [year month day]}] (str year \"/\" month \"/\" day))}\n```\n\nThe above is useful in conjunction with the `:save-fn` key that allows you to supply a custom function for saving the value.\nFor example, if you wanted to use a JavaScript date object, you could do the following:\n\n```clojure\n[:div.input-group.date.datepicker.clickable\n {:field       :datepicker\n  :id          :reminder\n  :date-format (fn [date]\n                 (str (.getDate date) \"/\"\n                      (inc (.getMonth date)) \"/\"\n                      (.getFullYear date)))\n  :save-fn     (fn [current-date {:keys [year month day]}]\n                 (if current-date\n                   (doto (js/Date.)\n                     (.setFullYear year)\n                     (.setMonth (dec month))\n                     (.setDate day)\n                     (.setHours (.getHours current-date))\n                     (.setMinutes (.getMinutes current-date)))\n                   (js/Date. year (dec month) day)))\n  :auto-close? true}]\n```\n\nNote that you need to return a new date object in updates for the component to repaint.\n\n\nDatepicker takes an optional `:lang` key which you can use to set the locale of the datepicker. There are currently English, Russian, German, French, Spanish, Portuguese, Finnish and Dutch built in translations. To use a built-in language pass in `:lang` with a keyword as in the following table:\n\n| Language | Keyword |\n|----------|---------|\n| English | `:en-US` (default) |\n| Russian | `:ru-RU` |\n| German  | `:de-DE` |\n| French  | `:fr-FR` |\n| Spanish | `:es-ES` |\n| Portuguese | `:pt-PT` |\n| Finnish | `:fi-FI` |\n| Dutch   | `:nl-NL` |\n\nExample of using a built in language locale:\n\n```Clojure\n{:field :datepicker :id :date :date-format \"yyyy/mm/dd\" :inline true :lang :ru-RU}\n```\n\nYou can also provide a custom locale hash-map to the datepicker. `:first-day` marks the first day of the week starting from Sunday as 0. All of the keys must be specified.\n\nExample of using a custom locale hash-map:\n\n```clojure\n{:field :datepicker :id :date :date-format \"yyyy/mm/dd\" :inline true :lang\n {:days        [\"First\" \"Second\" \"Third\" \"Fourth\" \"Fifth\" \"Sixth\" \"Seventh\"]\n  :days-short  [\"1st\" \"2nd\" \"3rd\" \"4th\" \"5th\" \"6th\" \"7th\"]\n  :months      [\"Month-one\" \"Month-two\" \"Month-three\" \"Month-four\" \"Month-five\" \"Month-six\"\n                \"Month-seven\" \"Month-eight\" \"Month-nine\" \"Month-ten\" \"Month-eleven\" \"Month-twelve\"]\n  :months-short [\"M1\" \"M2\" \"M3\" \"M4\" \"M5\" \"M6\" \"M7\" \"M8\" \"M9\" \"M10\" \"M11\" \"M12\"]\n  :first-day 0}}\n```\n\nThe datepicker requires additional CSS in order to be rendered correctly. The default CSS is provided\nin `reagent-forms.css` in the resource path. Simply make sure that it's included on the page.\nThe File can be read using:\n\n```clojure\n(-\u003e \"reagent-forms.css\" clojure.java.io/resource slurp)\n```\n\n#### :container\n\nThe container element can be used to group different element.\nThe container can be used to set the visibility of multiple elements.\n\n```clojure\n[:div.form-group\n {:field :container\n  :visible? #(:show-name? %)}\n [:input {:field :text :id :first-name}]\n [:input {:field :text :id :last-name}]]\n```\n\n### Validation\n\nA validator function can be attached to a component using the `:validator` keyword. This function accepts the current state of the document, and returns a collection of classes that will be appended to the element:\n\n```clojure\n[:input\n {:field :text\n  :id :person.name.first\n  :validator (fn [doc]\n               (when (-\u003e doc :person :name :first empty?)\n                 [\"error\"]))}]\n```\n\n\n### Setting component visibility\n\nThe components may  supply an optional `:visible?` key in their attributes that points to a decision function.\nThe function is expected to take the current value of the document and produce a truthy value that will be used to decide whether the component should be rendered, eg:\n\n```clojure\n(def form\n  [:div\n   [:input {:field :text\n            :id :foo}]\n   [:input {:field :text\n            :visible? (fn [doc] (empty? (:foo doc)))\n            :id :bar}]])\n```\n\n### Updating attributes\n\nThe `:set-attributes` key can be used in cases where you need to do an arbitrary update on the attributes of the component. The key must point to a function that\naccepts the current value of the document and the map of the attributes for the component. The function must return an updated attribute map:\n\n```clojure\n[:div\n [:input {:field :text\n         :id :person.name.first\n         :validator (fn [doc]\n                      (when (= \"Bob\" (-\u003e doc :person :name :first))\n                        [\"error\"]))}]\n [:input {:field :text\n         :id :person.name.last\n         :set-attributes (fn [doc attrs]\n                           (assoc attrs :disabled (= \"Bob\" (-\u003e doc :person :name :first))))}]]\n```\n\nAbove example disables the last name input when the value of the first name input is \"Bob\".\n\n## Binding the form to a document\n\nThe field components behave just like any other Reagent components and can be mixed with them freely. A complete form example can be seen below.\n\nForm elements can be bound to a nested structure by using the `.` as a path separator. For example, the following component `[:input {:field :text :id :person.first-name}]` binds to the following path in the state atom `{:person {:first-name \u003cfield-value\u003e}}`\n\n\n```clojure\n(defn row [label input]\n  [:div.row\n    [:div.col-md-2 [:label label]]\n    [:div.col-md-5 input]])\n\n(def form-template\n  [:div\n   (row \"first name\" [:input {:field :text :id :first-name}])\n   (row \"last name\" [:input {:field :text :id :last-name}])\n   (row \"age\" [:input {:field :numeric :id :age}])\n   (row \"email\" [:input {:field :email :id :email}])\n   (row \"comments\" [:textarea {:field :textarea :id :comments}])])\n```\n\n**important note**\n\nThe templates are eagerly evaluated, and you should always call the helper functions as in the example above instead of putting them in a vector. These will be replaced by Reagent components when the `bind-fields` is called to compile the template.\n\nOnce a form template is created it can be bound to a document using the `bind-fields` function:\n\n```clojure\n(ns myform.core\n  (:require [reagent-forms.core :refer [bind-fields]]\n            [reagent.core :as r]))\n\n(defn form []\n  (let [doc (r/atom {})]\n    (fn []\n      [:div\n       [:div.page-header [:h1 \"Reagent Form\"]]\n       [bind-fields form-template doc]\n       [:label (str @doc)]])))\n\n(reagent/render-component [form] (.getElementById js/document \"container\"))\n```\n\nThe form can be initialized with a populated document, and the fields will be initialize with the values found there:\n\n```clojure\n(def form-template\n  [:div\n   (row \"first name\"\n        [:input.form-control {:field :text :id :first-name}])\n   (row \"last name\"\n        [:input.form-control {:field :text :id :last-name}])\n   (row \"age\"\n        [:input.form-control {:field :numeric :id :age}])\n   (row \"email\"\n        [:input.form-control {:field :email :id :email}])\n   (row \"comments\"\n        [:textarea.form-control {:field :textarea :id :comments}])])\n\n(defn form []\n  (let [doc (atom {:first-name \"John\" :last-name \"Doe\" :age 35})]\n    (fn []\n      [:div\n       [:div.page-header [:h1 \"Reagent Form\"]]\n       [bind-fields form-template doc]\n       [:label (str @doc)]])))\n```\n\n## Adding events\n\nThe `bind-fields` function accepts optional events. Events are triggered whenever the document is updated, and will be executed in the order they are listed. Each event sees the document modified by its predecessor.\n\nThe event must take 3 parameters, which are the `id`, the `path`, the `value`, and the `document`. The `id` matches the `:id` of the field, the `path` is the path of the field in the document, the `value` represent the value that was changed in the form, and the document contains the state of the form. The event can either return an updated document or `nil`, when `nil` is returned then the state of the document is unmodified.\n\nThe following is an example of an event to calculate the value of the `:bmi` key when the `:weight` and `:height` keys are populated:\n\n```clojure\n(defn row [label input]\n  [:div.row\n    [:div.col-md-2 [:label label]]\n    [:div.col-md-5 input]])\n\n(def form-template\n [:div\n   [:h3 \"BMI Calculator\"]\n   (row \"Height\" [:input {:field :numeric :id :height}])\n   (row \"Weight\" [:input {:field :numeric :id :weight}])\n   (row \"BMI\" [:input {:field :numeric :id :bmi :disabled true}])])\n\n[bind-fields\n  form-template\n  doc\n  (fn [id path value {:keys [weight height] :as doc}]\n    (when (and (some #{id} [:height :weight]) weight height)\n      (assoc-in doc [:bmi] (/ weight (* height height)))))]\n```\n\n## Using with re-frame\n\nYou can provide a custom map of event functions to `bind-fields` to use reagent-forms with a library like `re-frame`. In that case, reagent-forms will not hold any internal state and functions provided by you will be used to get, save, and update the field's value. Here's an example:\n\n```clojure\n(ns foo.bar\n  (:require [re-frame.core :as re-frame]\n            [reagent-forms.core :refer [bind-fields]]))\n\n; re-frame events\n(re-frame/reg-event-db\n :init\n (fn [_ _]\n   {:doc {}}))\n\n(re-frame/reg-sub\n :doc\n (fn [db _]\n   (:doc db)))\n\n(re-frame/reg-sub\n:value\n:\u003c- [:doc]\n(fn [doc [_ path]]\n  (get-in doc path)))\n\n(re-frame/reg-event-db\n :set-value\n (fn [db [_ path value]]\n   (assoc-in db (into [:doc] path) value)))\n\n(re-frame/reg-event-db\n :update-value\n (fn [db [_ f path value]]\n   (update-in db (into [:doc] path) f value)))\n\n; Functions that will be called by each individual form field with an id and a value\n(def events\n  {:get (fn [path] @(re-frame/subscribe [:value path]))\n   :save! (fn [path value] (re-frame/dispatch [:set-value path value]))\n   :update! (fn [path save-fn value]\n              ; save-fn should accept two arguments: old-value, new-value\n              (re-frame/dispatch [:update-value save-fn path value]))\n   :doc (fn [] @(re-frame/subscribe [:doc]))})\n\n; bind-fields called with a form and a map of custom events\n(defn foo\n  []\n  [bind-fields\n   [:div\n    [:input {:field  :text\n             :id     :person.name.first\n             :valid? (fn [doc]\n                       (when (= \"Bob\" (-\u003e doc :person :name :first))\n                         [\"error\"]))}]\n    [:input {:field :text\n             :id    :person.name.last}]]\n   events])\n```\n\n### managing visibility\n\nElement visibility can be set by either providing the id in a document that will be\ntreated as a truthy value, or a function:\n\n```clojure\n(re-frame/reg-event-db\n :toggle-foo\n (fn [db _]\n   (update-in db [:doc :foo] not)))\n\n(re-frame/reg-sub\n :bar-visible?\n (fn [db _]\n   (:bar db)))\n\n(re-frame/reg-event-db\n :toggle-bar\n (fn [db _]\n   (update db :bar not)))\n\n(def form\n [:div\n  [:input {:field :text\n           :id :foo-input\n           :visible? :foo}]\n  [:input {:field :text\n           :id :bar-input\n           :visible? (fn [doc] @(re-frame/subscribe [:bar-visible?]))}]\n\n(defn page\n  []\n  [:div\n   [bind-fields\n    [:input {:field :text\n             :id :foo-input\n             :visible? :foo-input-visible?}]\n     event-fns]\n   [:button\n    {:on-click #(re-frame/dispatch [:toggle-foo])}\n    \"toggle foo\"]\n   [:button\n    {:on-click #(re-frame/dispatch [:toggle-bar])}\n    \"toggle bar\"]])\n```\n\n### adding business rules\n\nIf you're using re-frame, then it's recommended that you use re-frame events to trigger recalculation of fields in the form. For example, let's take a look at a calculated BMI field:\n\n```clojure\n(re-frame/reg-sub\n :value\n :\u003c- [:doc]\n (fn [doc [_ path]]\n   (get-in doc path)))\n\n(defn bmi [{:keys [weight height] :as doc}]\n  (assoc doc :bmi (/ weight (* height height))))\n\n(defmulti rule (fn [_ path _] path))\n\n(defmethod rule [:height] [doc path value]\n  (bmi doc))\n\n(defmethod rule [:weight] [doc path value]\n  (bmi doc))\n\n(defmethod rule :default [doc path value]\n  doc)\n\n(re-frame/reg-event-db\n :set-value\n (fn [{:keys [doc] :as db} [_ path value]]\n   (-\u003e db\n       (assoc-in (into [:doc] path) value)\n       (update :doc rule path value))))\n\n(def events\n  {:get (fn [path] @(re-frame/subscribe [:value path]))\n   :save! (fn [path value] (re-frame/dispatch [:set-value path value]))  \n   :doc (fn [] @(re-frame/subscribe [:doc]))})\n\n(defn row [label input]\n  [:div\n   [:div [:label label]]\n   [:div input]])\n\n(def form-template\n  [:div\n   [:h3 \"BMI Calculator\"]\n   (row \"Height\" [:input {:field :numeric :id :height}])\n   (row \"Weight\" [:input {:field :numeric :id :weight}])\n   (row \"BMI\" [:label {:field :label :id :bmi}])])\n\n(defn home-page []\n  [:div\n   [:h2 \"BMI example\"]\n   [bind-fields form-template events]])\n```\n\nThe `rule` multiemthod will be triggered when the `:set-value` event is called, and it will calculate BMI any time the height or weight is updated.\n\n## Adding custom fields\n\nCustom fields can be added by implementing the `reagent-forms.core/init-field` multimethod. The method must\ntake two parameters, where the first parameter is the field component and the second is the options.\n\nBy default the options will contain the `get` and the `save!`, and `update!` keys. The `get` key points to a function that accepts an id and returns the document value associated with it. The `save!` function accepts an id and a value that will be associated with it. The `update!` function accepts an id, a function that will handle the update, and the value. The function handling the update will receive the old and the new values.\n\n## Using adapters\n\nAdapters can be provided to fields so as to create custom storage formats for field values. These are a pair of functions passed to the field through the keys `:in-fn` and `:out-fn`. `:in-fn` modifies the stored item so that the field can make use of it while `:out-fn` modifies the output of the field before it is stored. For example, in order to use a native `js/Date` object as the storage format, the datepicker can be initialized thusly:\n\n```clojure\n[:div {:field :datepicker :id :birthday :date-format \"yyyy/mm/dd\" :inline true\n       :in-fn #(when % {:year (.getFullYear %) :month (.getMonth %) :day (.getDate %)})\n       :out-fn #(when % (js/Date (:year %) (:month %) (:day %)))}]\n```\n\nAdapters may be passed nulls so they must be able to handle those.\n\n## Mobile Gotchas\n\nSafari on iOS will have a 300ms delay for `:on-click` events, it's possible to set a custom trigger event using the `:touch-event` key. See [here](http://facebook.github.io/react/docs/events.html) for the list of events available in React. For example, if we wanted to use `:on-touch-start` instead of `:on-click` to trigger the event then we could do the following:\n\n```clojure\n[:input.form-control {:field :text :id :first-name :touch-event :on-touch-start}]\n```\n\nNote that you will also have to set the style of `cursor: pointer` for any elements other than buttons in order for events to work on iOS.\n\nThe [TapEventPlugin](https://github.com/zilverline/react-tap-event-plugin) for react is another option for creating responsive events, until the functionality becomes available in React itself.\n\n## Testing\nThis project uses [`Doo`](https://github.com/bensu/doo) for running the tests.\nYou must install one of the Doo-supported environments, refer to [the docs](https://github.com/bensu/doo#setting-up-environments) for details.\nTo run the tests, for example using Phantom, do:\n\n```\nlein doo slimer test\n```\n\n## License\n\nCopyright © 2018 Dmitri Sotnikov\n\nDistributed under the Eclipse Public License either version 1.0 or (at your option) any later version.\n","funding_links":[],"categories":["Awesome ClojureScript"],"sub_categories":["Miscellaneous"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Freagent-project%2Freagent-forms","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Freagent-project%2Freagent-forms","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Freagent-project%2Freagent-forms/lists"}