├── .idea
├── .name
├── copyright
│ └── profiles_settings.xml
├── encodings.xml
├── vcs.xml
├── modules.xml
├── inspectionProfiles
│ ├── profiles_settings.xml
│ └── Project_Default.xml
├── misc.xml
├── runConfigurations
│ └── lein_doo_chrome_test_auto.xml
└── compiler.xml
├── .travis.yml
├── src
└── cljs
│ └── free_form
│ ├── extension.cljs
│ ├── debug.cljs
│ ├── re_frame.cljs
│ ├── util.cljs
│ ├── core.cljs
│ └── bootstrap_3.cljs
├── test
└── cljs
│ └── free_form
│ ├── runner.cljs
│ └── core_test.cljs
├── .gitignore
├── project.clj
├── free-form.iml
├── LICENSE
└── README.md
/.idea/.name:
--------------------------------------------------------------------------------
1 | free-form
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: clojure
2 | script: lein doo phantom test once
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/cljs/free_form/extension.cljs:
--------------------------------------------------------------------------------
1 | ;;;; Copyright © 2017 José Pablo Fernández Silva
2 |
3 | (ns free-form.extension)
4 |
5 | (defmulti extension (fn [extension-name _inner-fn] extension-name))
6 |
7 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/test/cljs/free_form/runner.cljs:
--------------------------------------------------------------------------------
1 | ;;;; Copyright © 2016-2017 José Pablo Fernández Silva
2 |
3 | (ns free-form.runner
4 | (:require [doo.runner :refer-macros [doo-tests]]
5 | [free-form.core-test]))
6 |
7 | (doo-tests 'free-form.core-test)
8 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/cljs/free_form/debug.cljs:
--------------------------------------------------------------------------------
1 | ;;;; Copyright © 2017 José Pablo Fernández Silva
2 |
3 | (ns free-form.debug
4 | (:require [free-form.extension :as extension]))
5 |
6 | (defmethod extension/extension :debug [_extension-name inner-fn]
7 | (fn [html]
8 | (println "Before:" html)
9 | (let [html (inner-fn html)]
10 | (println "After" html)
11 | html)))
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /classes
3 | /out
4 | /checkouts
5 | pom.xml
6 | pom.xml.asc
7 | *.jar
8 | *.class
9 | /.lein-*
10 | /.nrepl-port
11 | .hgignore
12 | .hg/
13 |
14 | # IntelliJ https://github.com/github/gitignore/blob/master/Global/JetBrains.gitignore
15 | .idea/workspace.xml
16 | .idea/tasks.xml
17 | .idea/dictionaries
18 | .idea/libraries
19 | .idea/replstate.xml
20 | *.ipr
21 | *.iws
22 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/lein_doo_chrome_test_auto.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/cljs/free_form/re_frame.cljs:
--------------------------------------------------------------------------------
1 | ;;;; Copyright © 2015-2017 José Pablo Fernández Silva
2 |
3 | (ns free-form.re-frame
4 | (:require [free-form.core :as core]
5 | [re-frame.core :as re-frame]))
6 |
7 | (defn form [& args]
8 | (let [event (nth args 2)
9 | re-frame-event-generator (fn [keys value]
10 | (let [event-v (cond
11 | (fn? event) (event keys value)
12 | (vector? event) (conj event keys value)
13 | :else [event keys value])]
14 | (re-frame/dispatch event-v)))
15 | args (assoc (vec args) 2 re-frame-event-generator)]
16 | (into [core/form] args)))
17 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/cljs/free_form/util.cljs:
--------------------------------------------------------------------------------
1 | ;;;; Copyright © 2017 José Pablo Fernández Silva
2 |
3 | (ns free-form.util)
4 |
5 | (defn field? [node]
6 | (and (coll? node) (= :free-form/field (first node))))
7 |
8 | (defn key->keys [m]
9 | (if (contains? m :key)
10 | (if (contains? m :keys)
11 | (throw (js/Error. "key->keys expects a map with :key or :keys, not both"))
12 | (assoc m :keys [(:key m)]))
13 | m))
14 |
15 | (def attributes-index
16 | "The second element in structure that represents an input is the attributes, as in :type, :key, etc."
17 | 1)
18 |
19 | ; Not needed yet, but might be needed in the future
20 | #_(defn- remove-free-form-attribute [node attr-location attr-name]
21 | (let [node (update-in node [attributes-index attr-location] dissoc attr-name)]
22 | (if (empty? (get-in node [attributes-index attr-location]))
23 | (update-in node [attributes-index] dissoc attr-location)
24 | node)))
25 |
--------------------------------------------------------------------------------
/project.clj:
--------------------------------------------------------------------------------
1 | ;;;; Copyright © 2015-2017 José Pablo Fernández Silva
2 |
3 | (defproject com.pupeno/free-form "0.6.0"
4 | :description "Library for building forms with Reagent or Re-frame."
5 | :url "https://github.com/pupeno/free-form"
6 | :license {:name "Eclipse Public License"
7 | :url "http://www.eclipse.org/legal/epl-v10.html"}
8 |
9 | :lein-release {:deploy-via :clojars}
10 | :signing {:gpg-key "71E6E789"}
11 | :scm {:name "git"
12 | :url "https://github.com/pupeno/free-form"}
13 |
14 | :dependencies [[org.clojure/clojurescript "1.9.293" :scope "provided"]
15 | [org.clojure/clojure "1.8.0" :scope "provided"]
16 | [reagent "0.6.0" :scope "provided"]
17 | [re-frame "0.8.0" :scope "provided"]
18 | [doo "0.1.7" :scope "provided"]]
19 | :plugins [[lein-cljsbuild "1.1.4"]
20 | [lein-doo "0.1.7"]]
21 |
22 | :source-paths ["src/cljs"]
23 | :cljsbuild {:builds {:test {:source-paths ["src/cljs" "test/cljs"]
24 | :compiler {:main free-form.runner
25 | :output-to "out/free_form.js"
26 | :optimizations :none}}}}
27 |
28 | :doo {:build "test"})
29 |
--------------------------------------------------------------------------------
/free-form.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/cljs/free_form/core.cljs:
--------------------------------------------------------------------------------
1 | ;;;; Copyright © 2015-2017 José Pablo Fernández Silva
2 |
3 | (ns free-form.core
4 | (:require clojure.string
5 | [clojure.walk :refer [postwalk prewalk]]
6 | [free-form.extension :as extension]
7 | [free-form.util :refer [field? key->keys attributes-index]]))
8 |
9 | (defn- extract-attributes [node key]
10 | (let [attributes (get node attributes-index)
11 | re-attributes (key attributes)
12 | attributes (dissoc attributes key)
13 | keys (or (:keys re-attributes) [(:key re-attributes)])]
14 | [attributes re-attributes keys]))
15 |
16 | (defn- input? [node]
17 | (and (coll? node)
18 | (contains? (second node) :free-form/input)))
19 |
20 | (defn- js-event-value [event]
21 | (let [target (.-target event)]
22 | (case (.-type target)
23 | "checkbox" (.-checked target)
24 | (.-value target))))
25 |
26 | (defn- extract-event-value [event]
27 | (if (or (boolean? event)
28 | (string? event))
29 | event ; React-toolbox generates events that already contain a stracted string of the value as the first paramenter
30 | (js-event-value event))) ; for all other cases, we extract it ourselves.
31 |
32 | (defn- first-non-nil [& coll]
33 | (first (filter (complement nil?) coll)))
34 |
35 | (defn- bind-input [values errors on-change node]
36 | (if (not (input? node))
37 | node
38 | (let [[attributes free-form-attributes keys] (extract-attributes node :free-form/input)
39 | {:keys [value-on error-on extra-error-keys]} free-form-attributes
40 | on-change-fn #(on-change keys (extract-event-value %1))
41 | value-on (or value-on (case (:type attributes)
42 | (:checkbox :radio) :default-checked
43 | :value))
44 | value (case (:type attributes)
45 | :checkbox (= true (get-in values keys))
46 | :radio (= (:value attributes) (get-in values keys))
47 | (first-non-nil (get-in values keys) (:blank-value free-form-attributes) ""))
48 | input-errors (get-in errors keys)]
49 | (assoc node attributes-index
50 | (cond-> attributes
51 | true (assoc :on-change on-change-fn)
52 | true (assoc value-on value)
53 | (and error-on input-errors) (assoc error-on (clojure.string/join " " input-errors))
54 | (and extra-error-keys (some #(get-in errors %) extra-error-keys)) (assoc error-on " "))))))
55 |
56 | (defn- error-class?
57 | "Tests whether the node should be marked with an error class should the field have an associated error."
58 | [node]
59 | (and (coll? node)
60 | (contains? (second node) :free-form/error-class)))
61 |
62 | (defn- bind-error-class [errors node]
63 | (if (not (error-class? node))
64 | node
65 | (let [[attributes re-attributes keys] (extract-attributes node :free-form/error-class)]
66 | (assoc node attributes-index
67 | (if (not-any? #(get-in errors %) (conj (:extra-keys re-attributes) keys))
68 | attributes
69 | (update attributes :class #(str (or (:error re-attributes) "error") %)))))))
70 |
71 | (defn- error-messages?
72 | [node]
73 | (and (coll? node)
74 | (contains? (second node) :free-form/error-message)))
75 |
76 | (defn- bind-error-messages [errors node]
77 | (if (not (error-messages? node))
78 | node
79 | (let [[attributes _ keys] (extract-attributes node :free-form/error-message)]
80 | (if-let [errors (get-in errors keys)]
81 | (vec (concat
82 | (drop-last (assoc node attributes-index attributes))
83 | (map #(conj (get node 2) %) errors)))
84 | nil))))
85 |
86 | (defn- warn-of-leftovers [node]
87 | (let [attrs (get node attributes-index)]
88 | (when (and (map? attrs)
89 | (some #(= "free-form" (namespace %)) (keys attrs)))
90 | (js/console.error "There are free-form-looking leftovers on" (pr-str node))))
91 | node)
92 |
93 | (defn form
94 | ([values errors on-change html]
95 | (form values errors on-change [] html))
96 | ([values errors on-change extensions html]
97 | (let [errors (or errors {})
98 | extensions (if (sequential? extensions) extensions [extensions])
99 | inner-fn (fn [html]
100 | (->> html
101 | (postwalk #(bind-input values errors on-change %))
102 | (postwalk #(bind-error-class errors %))
103 | (postwalk #(bind-error-messages errors %))))]
104 | (postwalk #(warn-of-leftovers %)
105 | ((reduce #(extension/extension %2 %1) inner-fn extensions) html)))))
106 |
--------------------------------------------------------------------------------
/src/cljs/free_form/bootstrap_3.cljs:
--------------------------------------------------------------------------------
1 | ;;;; Copyright © 2017 José Pablo Fernández Silva
2 |
3 | (ns free-form.bootstrap-3
4 | (:require [clojure.walk :refer [postwalk prewalk]]
5 | [clojure.string :as s]
6 | [free-form.util :refer [field? key->keys attributes-index]]
7 | [free-form.extension :as extension]))
8 |
9 | (defn- expand-bootstrap-3-input [id keys type placeholder options]
10 | (case type
11 | :select [:select.form-control {:free-form/input {:keys keys}
12 | :type type
13 | :id id
14 | :placeholder placeholder}
15 | (letfn [(generate-option [[value name]]
16 | (if (sequential? name)
17 | ^{:key value} [:optgroup {:label value}
18 | (map generate-option (partition 2 name))]
19 | ^{:key value} [:option {:value value} name]))]
20 | (map generate-option (partition 2 options)))]
21 | :textarea [:textarea.form-control {:free-form/input {:keys keys}
22 | :type type
23 | :id id}]
24 | :checkbox [:input {:free-form/input {:keys keys}
25 | :type type
26 | :id id}]
27 | :radio (map (fn [[value name]]
28 | ^{:name name}
29 | [:input {:free-form/input {:keys keys}
30 | :type type
31 | :name id
32 | :value value}])
33 | (partition 2 options))
34 | [:input.form-control {:free-form/input {:keys keys}
35 | :type type
36 | :id id
37 | :placeholder placeholder}]))
38 |
39 | (defn- expand-bootstrap-3-fields [node]
40 | (if (field? node)
41 | (let [{:keys [type keys extra-validation-error-keys label placeholder options]} (key->keys (second node))
42 | id (s/join "-" (map name keys))]
43 | (case type
44 | :checkbox [:div.checkbox {:free-form/error-class {:keys keys
45 | :extra-keys extra-validation-error-keys
46 | :error "has-error"}}
47 | [:label (expand-bootstrap-3-input id keys type placeholder options) label]
48 | [:div.text-danger {:free-form/error-message {:keys keys}} [:p]]]
49 | :radio [:div {:free-form/error-class {:keys keys
50 | :extra-keys extra-validation-error-keys
51 | :error "has-error"}}
52 | [:label label]
53 | (map (fn [input]
54 | ^{:key (str id "-" (get-in input [1 :value]))}
55 | [:div.radio
56 | [:label input (:name (meta input))]])
57 | (expand-bootstrap-3-input id keys type placeholder options))
58 | [:div.text-danger {:free-form/error-message {:keys keys}} [:p]]]
59 | [:div.form-group {:free-form/error-class {:keys keys :extra-keys extra-validation-error-keys :error "has-error"}}
60 | [:label.control-label {:for id} label]
61 | (expand-bootstrap-3-input id keys type placeholder options)
62 | [:div.text-danger {:free-form/error-message {:keys keys}} [:p]]]))
63 | node))
64 |
65 | (defn- expand-bootstrap-3-horizontal-fields [node]
66 | (if (field? node)
67 | (let [{:keys [type keys extra-validation-error-keys label placeholder options]} (key->keys (second node))
68 | id (s/join "-" (map name keys))]
69 | (case type
70 | :checkbox [:div.form-group {:free-form/error-class {:keys keys
71 | :extra-keys extra-validation-error-keys
72 | :error "has-error"}}
73 | [:div.col-sm-offset-2.col-sm-10
74 | [:div.checkbox
75 | [:label (expand-bootstrap-3-input id keys type placeholder options) label]]
76 | [:div.text-danger {:free-form/error-message {:keys keys}} [:p]]]]
77 | :radio [:div.form-group {:free-form/error-class {:keys keys
78 | :extra-keys extra-validation-error-keys
79 | :error "has-error"}}
80 | [:label.col-sm-2.control-label label]
81 | (-> [:div.col-sm-10]
82 | (into (map (fn [input]
83 | ^{:key (str id "-" (get-in input [1 :value]))}
84 | [:div.radio
85 | [:label input (:name (meta input))]])
86 | (expand-bootstrap-3-input id keys type placeholder options)))
87 | (conj [:div.text-danger {:free-form/error-message {:keys keys}} [:p]]))]
88 | [:div.form-group {:free-form/error-class {:keys keys
89 | :extra-keys extra-validation-error-keys
90 | :error "has-error"}}
91 | [:label.col-sm-2.control-label {:for id} label]
92 | [:div.col-sm-10 (expand-bootstrap-3-input id keys type placeholder options)
93 | [:div.text-danger {:free-form/error-message {:keys keys}} [:p]]]]))
94 | node))
95 |
96 | (defn- expand-bootstrap-3-inline-fields [node]
97 | (if (field? node)
98 | (let [{:keys [type keys extra-validation-error-keys label placeholder options]} (key->keys (second node))
99 | id (s/join "-" (map name keys))]
100 | (case type
101 | :checkbox [:div.form-group
102 | [:label.checkbox-inline {:free-form/error-class {:keys keys
103 | :extra-keys extra-validation-error-keys
104 | :error "has-error"}}
105 | (expand-bootstrap-3-input id keys type placeholder options) label]
106 | [:div.text-danger {:free-form/error-message {:keys keys}} [:p]]]
107 | :radio [:div.form-group {:free-form/error-class {:keys keys
108 | :extra-keys extra-validation-error-keys
109 | :error "has-error"}}
110 | (when label [:label label])
111 | (map (fn [input]
112 | ^{:key (str id "-" (get-in input [1 :value]))}
113 | [:div.radio-inline
114 | [:label input (:name (meta input))]])
115 | (expand-bootstrap-3-input id keys type placeholder options))
116 | [:div.text-danger {:free-form/error-message {:keys keys}} [:p]]]
117 | [:div.form-group {:free-form/error-class {:keys keys
118 | :extra-keys extra-validation-error-keys
119 | :error "has-error"}}
120 | [:label.control-label {:for id} label]
121 | " "
122 | (expand-bootstrap-3-input id keys type placeholder options)
123 | " "
124 | [:div.text-danger {:free-form/error-message {:keys keys}} [:p]]]))
125 | node))
126 |
127 | (defn- bootstrap-3-form-horizontal? [node]
128 | (and (coll? node)
129 | (= :form.form-horizontal (first node))))
130 |
131 | (defn- bootstrap-3-form-inline? [node]
132 | (and (coll? node)
133 | (= :form.form-inline (first node))))
134 |
135 | (defn- expand-bootstrap-3-form [node]
136 | (cond (bootstrap-3-form-horizontal? node) (postwalk expand-bootstrap-3-horizontal-fields node)
137 | (bootstrap-3-form-inline? node) (postwalk expand-bootstrap-3-inline-fields node)
138 | :else (postwalk expand-bootstrap-3-fields node)))
139 |
140 | (defmethod extension/extension :bootstrap-3 [_extension-name inner-fn]
141 | (fn [html]
142 | (inner-fn (prewalk expand-bootstrap-3-form html))))
143 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
2 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
3 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
4 |
5 | 1. DEFINITIONS
6 |
7 | "Contribution" means:
8 |
9 | a) in the case of the initial Contributor, the initial code and
10 | documentation distributed under this Agreement, and
11 |
12 | b) in the case of each subsequent Contributor:
13 |
14 | i) changes to the Program, and
15 |
16 | ii) additions to the Program;
17 |
18 | where such changes and/or additions to the Program originate from and are
19 | distributed by that particular Contributor. A Contribution 'originates' from
20 | a Contributor if it was added to the Program by such Contributor itself or
21 | anyone acting on such Contributor's behalf. Contributions do not include
22 | additions to the Program which: (i) are separate modules of software
23 | distributed in conjunction with the Program under their own license
24 | agreement, and (ii) are not derivative works of the Program.
25 |
26 | "Contributor" means any person or entity that distributes the Program.
27 |
28 | "Licensed Patents" mean patent claims licensable by a Contributor which are
29 | necessarily infringed by the use or sale of its Contribution alone or when
30 | combined with the Program.
31 |
32 | "Program" means the Contributions distributed in accordance with this
33 | Agreement.
34 |
35 | "Recipient" means anyone who receives the Program under this Agreement,
36 | including all Contributors.
37 |
38 | 2. GRANT OF RIGHTS
39 |
40 | a) Subject to the terms of this Agreement, each Contributor hereby grants
41 | Recipient a non-exclusive, worldwide, royalty-free copyright license to
42 | reproduce, prepare derivative works of, publicly display, publicly perform,
43 | distribute and sublicense the Contribution of such Contributor, if any, and
44 | such derivative works, in source code and object code form.
45 |
46 | b) Subject to the terms of this Agreement, each Contributor hereby grants
47 | Recipient a non-exclusive, worldwide, royalty-free patent license under
48 | Licensed Patents to make, use, sell, offer to sell, import and otherwise
49 | transfer the Contribution of such Contributor, if any, in source code and
50 | object code form. This patent license shall apply to the combination of the
51 | Contribution and the Program if, at the time the Contribution is added by the
52 | Contributor, such addition of the Contribution causes such combination to be
53 | covered by the Licensed Patents. The patent license shall not apply to any
54 | other combinations which include the Contribution. No hardware per se is
55 | licensed hereunder.
56 |
57 | c) Recipient understands that although each Contributor grants the licenses
58 | to its Contributions set forth herein, no assurances are provided by any
59 | Contributor that the Program does not infringe the patent or other
60 | intellectual property rights of any other entity. Each Contributor disclaims
61 | any liability to Recipient for claims brought by any other entity based on
62 | infringement of intellectual property rights or otherwise. As a condition to
63 | exercising the rights and licenses granted hereunder, each Recipient hereby
64 | assumes sole responsibility to secure any other intellectual property rights
65 | needed, if any. For example, if a third party patent license is required to
66 | allow Recipient to distribute the Program, it is Recipient's responsibility
67 | to acquire that license before distributing the Program.
68 |
69 | d) Each Contributor represents that to its knowledge it has sufficient
70 | copyright rights in its Contribution, if any, to grant the copyright license
71 | set forth in this Agreement.
72 |
73 | 3. REQUIREMENTS
74 |
75 | A Contributor may choose to distribute the Program in object code form under
76 | its own license agreement, provided that:
77 |
78 | a) it complies with the terms and conditions of this Agreement; and
79 |
80 | b) its license agreement:
81 |
82 | i) effectively disclaims on behalf of all Contributors all warranties and
83 | conditions, express and implied, including warranties or conditions of title
84 | and non-infringement, and implied warranties or conditions of merchantability
85 | and fitness for a particular purpose;
86 |
87 | ii) effectively excludes on behalf of all Contributors all liability for
88 | damages, including direct, indirect, special, incidental and consequential
89 | damages, such as lost profits;
90 |
91 | iii) states that any provisions which differ from this Agreement are offered
92 | by that Contributor alone and not by any other party; and
93 |
94 | iv) states that source code for the Program is available from such
95 | Contributor, and informs licensees how to obtain it in a reasonable manner on
96 | or through a medium customarily used for software exchange.
97 |
98 | When the Program is made available in source code form:
99 |
100 | a) it must be made available under this Agreement; and
101 |
102 | b) a copy of this Agreement must be included with each copy of the Program.
103 |
104 | Contributors may not remove or alter any copyright notices contained within
105 | the Program.
106 |
107 | Each Contributor must identify itself as the originator of its Contribution,
108 | if any, in a manner that reasonably allows subsequent Recipients to identify
109 | the originator of the Contribution.
110 |
111 | 4. COMMERCIAL DISTRIBUTION
112 |
113 | Commercial distributors of software may accept certain responsibilities with
114 | respect to end users, business partners and the like. While this license is
115 | intended to facilitate the commercial use of the Program, the Contributor who
116 | includes the Program in a commercial product offering should do so in a
117 | manner which does not create potential liability for other Contributors.
118 | Therefore, if a Contributor includes the Program in a commercial product
119 | offering, such Contributor ("Commercial Contributor") hereby agrees to defend
120 | and indemnify every other Contributor ("Indemnified Contributor") against any
121 | losses, damages and costs (collectively "Losses") arising from claims,
122 | lawsuits and other legal actions brought by a third party against the
123 | Indemnified Contributor to the extent caused by the acts or omissions of such
124 | Commercial Contributor in connection with its distribution of the Program in
125 | a commercial product offering. The obligations in this section do not apply
126 | to any claims or Losses relating to any actual or alleged intellectual
127 | property infringement. In order to qualify, an Indemnified Contributor must:
128 | a) promptly notify the Commercial Contributor in writing of such claim, and
129 | b) allow the Commercial Contributor tocontrol, and cooperate with the
130 | Commercial Contributor in, the defense and any related settlement
131 | negotiations. The Indemnified Contributor may participate in any such claim
132 | at its own expense.
133 |
134 | For example, a Contributor might include the Program in a commercial product
135 | offering, Product X. That Contributor is then a Commercial Contributor. If
136 | that Commercial Contributor then makes performance claims, or offers
137 | warranties related to Product X, those performance claims and warranties are
138 | such Commercial Contributor's responsibility alone. Under this section, the
139 | Commercial Contributor would have to defend claims against the other
140 | Contributors related to those performance claims and warranties, and if a
141 | court requires any other Contributor to pay any damages as a result, the
142 | Commercial Contributor must pay those damages.
143 |
144 | 5. NO WARRANTY
145 |
146 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON
147 | AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER
148 | EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR
149 | CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A
150 | PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the
151 | appropriateness of using and distributing the Program and assumes all risks
152 | associated with its exercise of rights under this Agreement , including but
153 | not limited to the risks and costs of program errors, compliance with
154 | applicable laws, damage to or loss of data, programs or equipment, and
155 | unavailability or interruption of operations.
156 |
157 | 6. DISCLAIMER OF LIABILITY
158 |
159 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
160 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
161 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION
162 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
163 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
164 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
165 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY
166 | OF SUCH DAMAGES.
167 |
168 | 7. GENERAL
169 |
170 | If any provision of this Agreement is invalid or unenforceable under
171 | applicable law, it shall not affect the validity or enforceability of the
172 | remainder of the terms of this Agreement, and without further action by the
173 | parties hereto, such provision shall be reformed to the minimum extent
174 | necessary to make such provision valid and enforceable.
175 |
176 | If Recipient institutes patent litigation against any entity (including a
177 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself
178 | (excluding combinations of the Program with other software or hardware)
179 | infringes such Recipient's patent(s), then such Recipient's rights granted
180 | under Section 2(b) shall terminate as of the date such litigation is filed.
181 |
182 | All Recipient's rights under this Agreement shall terminate if it fails to
183 | comply with any of the material terms or conditions of this Agreement and
184 | does not cure such failure in a reasonable period of time after becoming
185 | aware of such noncompliance. If all Recipient's rights under this Agreement
186 | terminate, Recipient agrees to cease use and distribution of the Program as
187 | soon as reasonably practicable. However, Recipient's obligations under this
188 | Agreement and any licenses granted by Recipient relating to the Program shall
189 | continue and survive.
190 |
191 | Everyone is permitted to copy and distribute copies of this Agreement, but in
192 | order to avoid inconsistency the Agreement is copyrighted and may only be
193 | modified in the following manner. The Agreement Steward reserves the right to
194 | publish new versions (including revisions) of this Agreement from time to
195 | time. No one other than the Agreement Steward has the right to modify this
196 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The
197 | Eclipse Foundation may assign the responsibility to serve as the Agreement
198 | Steward to a suitable separate entity. Each new version of the Agreement will
199 | be given a distinguishing version number. The Program (including
200 | Contributions) may always be distributed subject to the version of the
201 | Agreement under which it was received. In addition, after a new version of
202 | the Agreement is published, Contributor may elect to distribute the Program
203 | (including its Contributions) under the new version. Except as expressly
204 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or
205 | licenses to the intellectual property of any Contributor under this
206 | Agreement, whether expressly, by implication, estoppel or otherwise. All
207 | rights in the Program not expressly granted under this Agreement are
208 | reserved.
209 |
210 | This Agreement is governed by the laws of the State of New York and the
211 | intellectual property laws of the United States of America. No party to this
212 | Agreement will bring a legal action under this Agreement more than one year
213 | after the cause of action arose. Each party waives its rights to a jury trial
214 | in any resulting litigation.
215 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Free-form
2 |
3 | [](https://github.com/pupeno/free-form)
4 | [](https://clojars.org/com.pupeno/free-form)
5 | [](https://travis-ci.org/pupeno/free-form)
6 |
7 | A ClojureScript library to help building web forms with [Reagent](https://reagent-project.github.io/) and optionally
8 | [re-frame](https://github.com/Day8/re-frame) (others are welcome too). The guiding principles behind [Free-form](https://github.com/pupeno/free-form)
9 | 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
10 | [Free-form Examples app](http://free-form-examples.pupeno.com).
11 |
12 | Free-form doesn't force any markup on you and thus creating your own is a first-class approach. To avoid code
13 | duplication there's an extension system that allows you to write forms in a very succinct way. A Bootstrap 3 extension
14 | comes with Free form but adding more is not hard. One of the advantages of these mechanism is that when you have a
15 | couple of fields that behave differently and need their own markup, you can still use a first class API and enjoy the
16 | advantage of value handling, validation errors and everything Free form has to offer.
17 |
18 | Free-form doesn't handle state for you. You need to decide how to handle state. Free-form comes with a re-frame module
19 | that helps you plug your form into the re-frame state. If you use the Reagent core you can handle state anyway you want.
20 | Other modules could be created to handle state in different ways, for example, one could be created to have a
21 | [private ratom the way Reagent-forms do it](https://github.com/pupeno/free-form/issues/1).
22 |
23 | Free-form supports but doesn't provide validation. It works very well, for example, with [Validateur](http://clojurevalidations.info/).
24 | Nothing special has been done for this, so it should be flexible for any library. If it isn't, please,
25 | [submit a bug report](https://github.com/pupeno/free-form/issues/new).
26 |
27 | The way this library works is that you write (or generate) the HTML template the way you normally do with Reagent, for
28 | example:
29 |
30 | ```clojure
31 | [:input {:type :email
32 | :id :email
33 | :placeholder "sam@example.com"}]
34 | ```
35 |
36 | which then you pepper with special keywords to trigger the activation of inputs, labels, validation, etc. For example,
37 | to make this input to connect to the email we would change it to:
38 |
39 | ```clojure
40 | [:input {:free-form/input {:key :email}
41 | :type :email
42 | :id :email
43 | :placeholder "sam@example.com"}]
44 | ```
45 |
46 | [Reagent-forms](https://github.com/reagent-project/reagent-forms) was a big inspiration but the way it handles state was
47 | not ideal in a re-frame scenario.
48 |
49 | ## Usage
50 |
51 | First, you have to include Free-form in your project:
52 |
53 | [](http://clojars.org/com.pupeno/free-form)
54 |
55 | To activate a form you call ```free-form.core/form``` passing the set of values to display when the form is shown for
56 | the first time, the set of errors to display, a callback function to receive changes to the state and the form itself.
57 | For example:
58 |
59 | ```clojure
60 | [free-form.core/form {:email "pupeno@example.com"}
61 | {:email ["Email addresses can't end in @example.com"]}
62 | (fn [keys value] (println "Value for" keys "changed to" value))
63 | [...]]
64 | ```
65 |
66 | Notice that it's using square brackets because it's a Reagent component. You are likely to pass the contents of ratoms
67 | so that the form will be connected to live data, like:
68 |
69 | ```clojure
70 | [free-form.core/form @values @errors save-state
71 | [...]]
72 | ```
73 |
74 | The form is just your traditional Reagent template:
75 |
76 | ```clojure
77 | [free-form.core/form @values @errors save-state
78 | [:label {:for :email} "Email"]
79 | [:input.form-control {:free-form/input {:key :email}
80 | :free-form/error-class {:key :text :error "error"}
81 | :type :email
82 | :id :email}]
83 | [:div.errors {:free-form/error-message {:key :email}} [:p.error]]]
84 | ```
85 |
86 | There are three special keywords added:
87 | * ```: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```.
88 | * ```: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.
89 | * ```: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"]]```
90 |
91 | ### re-frame
92 |
93 | When using Free-form with re-frame, the form is built in exactly the same way, but instead of having to code your own
94 | state management function, you can pass the name of the event to be triggered:
95 |
96 | ```clojure
97 | [free-form.re-frame/form @values @errors :update-state
98 | [...]]
99 | ```
100 |
101 | And the library will dispatch ```[:update-state keys new-value]```. If you need to pass extra arguments to the handler,
102 | specify it as a vector.
103 |
104 | ```clojure
105 | [free-form.re-frame/form @values @errors [:update :user]
106 | [...]]
107 | ```
108 |
109 | If you need to generate more involved events to
110 | dispatch, you can pass a function that will get the keys and the new value and generate the event to be dispatched. For
111 | example:
112 |
113 | ```clojure
114 | [free-form.re-frame/form @values @errors (fn [keys new-value] [:update :user keys new-value])
115 | [...]]
116 | ```
117 |
118 | ### Bootstrap 3
119 |
120 | You can manually generate Bootstrap 3 forms by using code such as:
121 |
122 | ```clojure
123 | [free-form.core/form @values @errors save-state
124 | [:form.form-horizontal
125 | [:div.form-group {:free-form/error-class {:key :email :error "has-error"}}
126 | [:label.col-sm-2.control-label {:for :email} "Email"]
127 | [:div.col-sm-10 [:input.form-control {:free-form/input {:key :email}
128 | :type :email
129 | :id :email}]
130 | [:div.text-danger {:free-form/error-message {:key :email}} [:p]]]]]]
131 | ````
132 |
133 | but since that pattern is so common, it is now supported by an extension:
134 |
135 | ```clojure
136 | (ns whatever
137 | (:require [free-form.core :as free-form]
138 | free-form.bootstrap-3))
139 |
140 | [free-form/form @values @errors save-state :bootstrap-3
141 | [:form.form-horizontal
142 | [:free-form/field {:type :email
143 | :key :email
144 | :label "Email"}]]]
145 | ````
146 |
147 | You need to require ```free-form.bootstrap-3``` for the extension to be available. The extra argument,
148 | ```:bootstrap-3``` is what triggers Bootstrap 3 generation and Free-form will automatically detect whether it's a
149 | [standard](http://free-form-examples.pupeno.com/reagent/bootstrap-3), [horizontal](http://free-form-examples.pupeno.com/reagent/bootstrap-3-horizontal)
150 | or [inline](http://free-form-examples.pupeno.com/reagent/bootstrap-3-inline) form.
151 |
152 | ### Debugging
153 |
154 | The debug extension just prints the form before and after any other processing happens.
155 |
156 | ```clojure
157 | (ns whatever
158 | (:require [free-form.core :as free-form]
159 | free-form.debug))
160 | ```
161 |
162 | ### Writing your own extensions
163 |
164 | There's a fourth optional argument to specify one or more extensions to be applied to the form. For example, with only
165 | one extension called bootstrap-3:
166 |
167 | ```clojure
168 | [free-form.core/form @values @errors save-state :bootstrap-3
169 | [...]]
170 | ````
171 |
172 | or with multiple:
173 |
174 | ```clojure
175 | [free-form.core/form @values @errors save-state [:bootstrap-3 :debug]
176 | [...]]
177 | ````
178 |
179 | Extensions essentially wrap the form and thus, order is important and they can be provided more than once. For example:
180 |
181 | ```clojure
182 | [free-form.core/form @values @errors save-state [:debug :bootstrap-3 :debug]
183 | [...]]
184 | ````
185 |
186 | would help you see what the Bootstrap 3 extension is doing.
187 |
188 | Extensions are implemented by adding a method to the multi-method free-form.extension/extension. This method will get
189 | the name of the extension and the function that process the form. This function gets the unprocessed html markup and
190 | returns the processed html structure. The extension should return a function that does essentially the same plus
191 | whatever the extension wants to do. This is a system similar to middlewares found in many libraries. For example:
192 |
193 | ```clojure
194 | (defmethod free-form.extension/extension :extension-name [_extension-name inner-fn]
195 | (fn [html]
196 | (do-something-else (inner-fn (do-something html)))))
197 | ```
198 |
199 | do-something would pre-process the raw structure and do-something-else would post-process the structure after all inner
200 | extensions and the main inner function have been called.
201 |
202 | See the [debug](https://github.com/pupeno/free-form/blob/master/src/cljs/free_form/debug.cljs) and the
203 | [Bootstrap 3 extension](https://github.com/pupeno/free-form/blob/master/src/cljs/free_form/bootsrap_3.cljs)s for
204 | examples.
205 |
206 | ## Users
207 |
208 | This is a completely incomplete list of people/projects using Free-form:
209 |
210 | - [Dashman](https://dashman.tech)
211 | - [Wieck](https://wieck.com)
212 | - [You?](mailto:pupeno@pupeno.com)
213 |
214 | ## Changelog
215 |
216 | ### v0.6.0 - 2017-08-03
217 | - Add support for checkbox and radio form elements, courtesy of [Scott Bauer](https://github.com/Bauerpauer): https://github.com/pupeno/free-form/pull/26
218 | - Add support for checkbox and radio buttons in the Bootstrap 3 extension.
219 |
220 | ### v0.5.0 - 2017-01-07
221 | - Extension system.
222 | - Bootstrap 3 provided as an extension.
223 | - Debug extension.
224 |
225 | ### v0.4.2 - 2016-11-12
226 | - Make all inputs controlled so changes can come from within our from outside.
227 |
228 | ### v0.4.1 - 2016-10-25
229 | - Added the sources directory to the project.clj so that the library is correctly packaged.
230 |
231 | ### v0.4.0 - 2016-10-24
232 | - Tested Free-form with re-frame 0.8.0 and Reagent 0.6.0.
233 | - Allow marking a field as invalid when another one is invalid with :extra-keys.
234 | - Added error messages to Bootstrap inline and horizontal forms.
235 | - Correctly specify dependencies on Clojure and ClojureScript to avoid fixing to a single version.
236 | - When there's no validation error, don't return the form template (invalid HTML), return nil instead.
237 | - After a form with Bootstrap has been processed, remove the option to trigger that processing (it's invalid HTML).
238 | - Show a JavaScript console error if there are any Free-form leftovers after all processing is done.
239 |
240 | ### v0.3.0 - 2016-08-16
241 | - Changed namespace from com.carouselapps to com.pupeno
242 | - Implemented selects.
243 | - Implemented text areas.
244 |
245 | ### v0.2.1 - 2016-08-22
246 | - Changed the metadata of the library to point to the new namespace.
247 |
248 | ### v0.2.0 - 2015-12-14
249 | - Started Bootstrap 3 support.
250 | - Change API from ```:free-form/field``` to ```:free-form/input```.
251 | - Created example app to help test, exercise and develop the library: http://free-form-examples.pupeno.com
252 |
253 | ### v0.1.1 - 2015-10-15
254 | - Fixed a bug when dealing with errors.
255 |
256 | ### v0.1.0 - 2015-10-11
257 | - Initial version extracted from [Ninja Tools](http://tools.screensaver.ninja).
258 |
259 | ## License
260 |
261 | Copyright © 2015-2017 José Pablo Fernández Silva
262 |
263 | Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.
264 |
--------------------------------------------------------------------------------
/test/cljs/free_form/core_test.cljs:
--------------------------------------------------------------------------------
1 | ;;;; Copyright © 2015-2017 José Pablo Fernández Silva
2 |
3 | (ns free-form.core-test
4 | (:require [clojure.test :refer [deftest testing is]]
5 | [clojure.walk :refer [prewalk]]
6 | [free-form.core :as free-form]))
7 |
8 | (defn- hide-on-change [form]
9 | (prewalk (fn [node] (if (contains? node :on-change)
10 | (assoc node :on-change :was-function)
11 | node))
12 | form))
13 |
14 | (deftest a-test
15 | (let [plain-reagent-form-template [:form {:noValidate true}
16 | [:div.errors {:free-form/error-message {:key :-general}} [:p.error]]
17 | [:div.plain-field {:free-form/error-class {:key :text :error "validation-errors"}}
18 | [:label {:for :text} "Text"]
19 | [:input {:free-form/input {:key :text}
20 | :type :text
21 | :id :text
22 | :placeholder "placeholder"}]
23 | [:div.errors {:free-form/error-message {:key :text}} [:p.error]]]
24 | [:div.plain-field {:free-form/error-class {:key :email :error "validation-errors"}}
25 | [:label {:for :email} "Email"]
26 | [:input {:free-form/input {:key :email}
27 | :type :email
28 | :id :email
29 | :placeholder "placeholder@example.com"}]
30 | [:div.errors {:free-form/error-message {:key :email}} [:p.error]]]
31 | [:div.plain-field {:free-form/error-class {:key :password :error "validation-errors"}}
32 | [:label {:for :password} "Password"]
33 | [:input {:free-form/input {:key :password}
34 | :type :password
35 | :id :password}]
36 | [:div.errors {:free-form/error-message {:key :password}} [:p.error]]]
37 | [:div.plain-field {:free-form/error-class {:key :select :error "validation-errors"}}
38 | [:label {:for :select} "Select"]
39 | [:select {:free-form/input {:key :select}
40 | :type :select
41 | :id :select}
42 | [:option]
43 | [:option {:value :dog} "Dog"]
44 | [:option {:value :cat} "Cat"]
45 | [:option {:value :squirrel} "Squirrel"]
46 | [:option {:value :giraffe} "Giraffe"]]
47 | [:div.errors {:free-form/error-message {:key :select}} [:p.error]]]
48 | [:div.plain-field {:free-form/error-class {:key :select-with-group :error "validation-errors"}}
49 | [:label {:for :select} "Select with groups"]
50 | [:select {:free-form/input {:key :select-with-group}
51 | :type :select
52 | :id :select-with-group}
53 | [:option]
54 | [:optgroup {:label "Numbers"}
55 | [:option {:value :one} "One"]
56 | [:option {:value :two} "Two"]
57 | [:option {:value :three} "Three"]
58 | [:option {:value :four} "Four"]]
59 | [:optgroup {:label "Leters"}
60 | [:option {:value :a} "A"]
61 | [:option {:value :b} "B"]
62 | [:option {:value :c} "C"]
63 | [:option {:value :d} "D"]]]
64 | [:div.errors {:free-form/error-message {:key :select-with-group}} [:p.error]]]
65 | [:div.plain-field {:free-form/error-class {:key :textarea :error "validation-errors"}}
66 | [:label {:for :text-area} "Text area"]
67 | [:textarea {:free-form/input {:key :textarea}
68 | :id :textarea}]
69 | [:div.errors {:free-form/error-message {:key :textarea}} [:p.error]]]
70 | [:div.plain-field {:free-form/error-class {:key [:t :e :x :t] :error "validation-errors"}}
71 | [:label {:for :text} "Text with deep keys"]
72 | [:input {:free-form/input {:keys [:t :e :x :t]}
73 | :type :text
74 | :id :text
75 | :placeholder "placeholder"}]
76 | [:div.errors {:free-form/error-message {:keys [:t :e :x :t]}} [:p.error]]]
77 | [:div.plain-field {:free-form/error-class {:key :text-with-extra-validation-errors :error "validation-errors"
78 | :extra-keys [[:text] [:-general]]}}
79 | [:label {:for :text-with-extra-validation-errors} "Text with extra validation errors"]
80 | [:input {:free-form/input {:key :text-with-extra-validation-errors}
81 | :type :text
82 | :id :text-with-extra-validation-errors
83 | :placeholder "This will be marked as a validation error also when Text and General have validation errors."}]
84 | [:div.errors {:free-form/error-message {:key :text-with-extra-validation-errors}} [:p.error]]]
85 | [:div {:free-form/error-class {:key :checkbox :error "validation-errors"}}
86 | [:input {:free-form/input {:key :checkbox}
87 | :type :checkbox
88 | :id :checkbox}]
89 | [:label {:for :checkbox} "Checkbox"]
90 | [:div.errors {:free-form/error-message {:key :checkbox}} [:p.error]]]
91 | [:div.plain-field {:free-form/error-class {:key :radio-buttons :error "validation-errors"}}
92 | [:label
93 | [:input {:free-form/input {:key :radio-buttons}
94 | :type :radio
95 | :name :radio-buttons
96 | :value "radio-option-1"}]
97 | "Radio Option 1"]
98 | [:label
99 | [:input {:free-form/input {:key :radio-buttons}
100 | :type :radio
101 | :name :radio-buttons
102 | :value "radio-option-2"}]
103 | "Radio Option 2"]
104 | [:label
105 | [:input {:free-form/input {:key :radio-buttons}
106 | :type :radio
107 | :name :radio-buttons
108 | :value "radio-option-3"}]
109 | "Radio Option 3"]
110 | [:div.errors {:free-form/error-message {:key :radio-buttons}} [:p.error]]]
111 | [:button "Button"]]]
112 |
113 | (testing "simple generation"
114 | (let [generated-input (hide-on-change
115 | (free-form/form {} {} (fn [_keys _value])
116 | plain-reagent-form-template))]
117 | (is (= generated-input
118 | [:form {:noValidate true}
119 | nil
120 | [:div.plain-field {}
121 | [:label {:for :text} "Text"]
122 | [:input {:type :text
123 | :id :text
124 | :placeholder "placeholder"
125 | :value ""
126 | :on-change :was-function}]
127 | nil]
128 | [:div.plain-field {}
129 | [:label {:for :email} "Email"]
130 | [:input {:type :email
131 | :id :email
132 | :placeholder "placeholder@example.com"
133 | :value ""
134 | :on-change :was-function}]
135 | nil]
136 | [:div.plain-field {}
137 | [:label {:for :password} "Password"]
138 | [:input {:type :password
139 | :id :password
140 | :value ""
141 | :on-change :was-function}]
142 | nil]
143 | [:div.plain-field {}
144 | [:label {:for :select} "Select"]
145 | [:select {:type :select
146 | :id :select
147 | :value ""
148 | :on-change :was-function}
149 | [:option]
150 | [:option {:value :dog} "Dog"]
151 | [:option {:value :cat} "Cat"]
152 | [:option {:value :squirrel} "Squirrel"]
153 | [:option {:value :giraffe} "Giraffe"]]
154 | nil]
155 | [:div.plain-field {}
156 | [:label {:for :select} "Select with groups"]
157 | [:select {:type :select
158 | :id :select-with-group
159 | :value ""
160 | :on-change :was-function}
161 | [:option]
162 | [:optgroup {:label "Numbers"}
163 | [:option {:value :one} "One"]
164 | [:option {:value :two} "Two"]
165 | [:option {:value :three} "Three"]
166 | [:option {:value :four} "Four"]]
167 | [:optgroup {:label "Leters"}
168 | [:option {:value :a} "A"]
169 | [:option {:value :b} "B"]
170 | [:option {:value :c} "C"]
171 | [:option {:value :d} "D"]]]
172 | nil]
173 | [:div.plain-field {}
174 | [:label {:for :text-area} "Text area"]
175 | [:textarea {:id :textarea
176 | :value ""
177 | :on-change :was-function}]
178 | nil]
179 | [:div.plain-field {}
180 | [:label {:for :text} "Text with deep keys"]
181 | [:input {:type :text
182 | :id :text
183 | :placeholder "placeholder"
184 | :value ""
185 | :on-change :was-function}]
186 | nil]
187 | [:div.plain-field {}
188 | [:label {:for :text-with-extra-validation-errors} "Text with extra validation errors"]
189 | [:input {:type :text
190 | :id :text-with-extra-validation-errors
191 | :placeholder "This will be marked as a validation error also when Text and General have validation errors."
192 | :value ""
193 | :on-change :was-function}]
194 | nil]
195 | [:div {}
196 | [:input {:type :checkbox
197 | :id :checkbox
198 | :default-checked false
199 | :on-change :was-function}]
200 | [:label {:for :checkbox} "Checkbox"]
201 | nil]
202 | [:div.plain-field {}
203 | [:label
204 | [:input {:type :radio
205 | :name :radio-buttons
206 | :value "radio-option-1"
207 | :default-checked false
208 | :on-change :was-function}]
209 | "Radio Option 1"]
210 | [:label
211 | [:input {:type :radio
212 | :name :radio-buttons
213 | :value "radio-option-2"
214 | :default-checked false
215 | :on-change :was-function}]
216 | "Radio Option 2"]
217 | [:label
218 | [:input {:type :radio
219 | :name :radio-buttons
220 | :value "radio-option-3"
221 | :default-checked false
222 | :on-change :was-function}]
223 | "Radio Option 3"]
224 | nil]
225 | [:button "Button"]]))))
226 |
227 | (testing "generation with initial data"
228 | (let [generated-input (hide-on-change
229 | (free-form/form {:text "Text value"
230 | :email "Email value"
231 | :password "Password value"
232 | ;:select "cat" ; TODO: enable this and fix generation, as it's broken right now.
233 | ;:select-with-group "two" ; TODO: enable this and fix generation, as it's broken right now.
234 | :textarea "Textarea value"
235 | :t {:e {:x {:t "Text with deep keys value"}}}
236 | :checkbox true
237 | :radio-buttons "radio-option-2"
238 | } {} (fn [_keys _value])
239 | plain-reagent-form-template))]
240 | (is (= generated-input
241 | [:form {:noValidate true}
242 | nil
243 | [:div.plain-field {}
244 | [:label {:for :text} "Text"]
245 | [:input {:type :text
246 | :id :text
247 | :placeholder "placeholder"
248 | :value "Text value"
249 | :on-change :was-function}] nil]
250 | [:div.plain-field {}
251 | [:label {:for :email} "Email"]
252 | [:input {:type :email
253 | :id :email
254 | :placeholder "placeholder@example.com"
255 | :value "Email value"
256 | :on-change :was-function}] nil]
257 | [:div.plain-field {}
258 | [:label {:for :password} "Password"]
259 | [:input {:type :password
260 | :id :password
261 | :value "Password value"
262 | :on-change :was-function}] nil]
263 | [:div.plain-field {}
264 | [:label {:for :select} "Select"]
265 | [:select {:type :select
266 | :id :select
267 | :value ""
268 | :on-change :was-function} [:option]
269 | [:option {:value :dog} "Dog"]
270 | [:option {:value :cat} "Cat"]
271 | [:option {:value :squirrel} "Squirrel"]
272 | [:option {:value :giraffe} "Giraffe"]] nil]
273 | [:div.plain-field {}
274 | [:label {:for :select} "Select with groups"]
275 | [:select {:type :select
276 | :id :select-with-group
277 | :value ""
278 | :on-change :was-function} [:option]
279 | [:optgroup {:label "Numbers"} [:option {:value :one} "One"]
280 | [:option {:value :two} "Two"]
281 | [:option {:value :three} "Three"]
282 | [:option {:value :four} "Four"]]
283 | [:optgroup {:label "Leters"} [:option {:value :a} "A"]
284 | [:option {:value :b} "B"]
285 | [:option {:value :c} "C"]
286 | [:option {:value :d} "D"]]] nil]
287 | [:div.plain-field {}
288 | [:label {:for :text-area} "Text area"]
289 | [:textarea {:id :textarea
290 | :value "Textarea value"
291 | :on-change :was-function}] nil]
292 | [:div.plain-field {}
293 | [:label {:for :text} "Text with deep keys"]
294 | [:input {:type :text
295 | :id :text
296 | :placeholder "placeholder"
297 | :value "Text with deep keys value"
298 | :on-change :was-function}] nil]
299 | [:div.plain-field {}
300 | [:label {:for :text-with-extra-validation-errors} "Text with extra validation errors"]
301 | [:input {:type :text
302 | :id :text-with-extra-validation-errors
303 | :placeholder "This will be marked as a validation error also when Text and General have validation errors."
304 | :value ""
305 | :on-change :was-function}] nil]
306 | [:div {}
307 | [:input {:type :checkbox
308 | :id :checkbox
309 | :default-checked true
310 | :on-change :was-function}]
311 | [:label {:for :checkbox} "Checkbox"] nil]
312 | [:div.plain-field {}
313 | [:label [:input {:type :radio
314 | :name :radio-buttons
315 | :value "radio-option-1"
316 | :default-checked false
317 | :on-change :was-function}] "Radio Option 1"]
318 | [:label [:input {:type :radio
319 | :name :radio-buttons
320 | :value "radio-option-2"
321 | :default-checked true
322 | :on-change :was-function}] "Radio Option 2"]
323 | [:label [:input {:type :radio
324 | :name :radio-buttons
325 | :value "radio-option-3"
326 | :default-checked false
327 | :on-change :was-function}] "Radio Option 3"] nil]
328 | [:button "Button"]]))))))
329 |
--------------------------------------------------------------------------------