add top level form specs

master
Josha von Gizycki 6 years ago
parent a355b9da14
commit d8b5e6d022

@ -18,8 +18,7 @@
(spec/def ::widget (spec/def ::widget
#{:input :select :checkbox :textarea :mselect :hidden}) #{:input :select :checkbox :textarea :mselect :hidden})
(spec/def ::from-req (spec/def ::from-req
(spec/and ifn? ;;#(= 1 (->> % meta :arglists (map count) first)) (spec/and ifn? #_(= 1 (->> % meta :arglists (map count) first))))
))
(spec/def ::to-form ::from-req) (spec/def ::to-form ::from-req)
(spec/def ::field (spec/def ::field
(spec/keys :opt-un [::label (spec/keys :opt-un [::label
@ -31,12 +30,19 @@
::spec])) ::spec]))
(spec/def ::fields (spec/def ::fields
(spec/map-of keyword? ::field)) (spec/map-of keyword? ::field))
(spec/def ::form-specs
(spec/coll-of ::spec))
(spec/def ::form (spec/def ::form
(spec/keys :req-un [::fields])) (spec/keys :req-un [::fields]
:opt-un [::form-specs]))
(defn spec-to-errmsg [label spec-key field-value] (defn err-msg [content]
[:section.flash--error [:section.flash--error
[:h2.flash__heading--error "Warning"] [:h2.flash__heading--error "Warning"]
content])
(defn spec-to-errmsg [label spec-key field-value]
(err-msg
(map (map
(fn [prob] (fn [prob]
[:p [:p
@ -45,7 +51,7 @@
" must comply to " " must comply to "
[:span.flash__pred (:pred prob)]]) [:span.flash__pred (:pred prob)]])
(:clojure.spec.alpha/problems (:clojure.spec.alpha/problems
(spec/explain-data spec-key field-value)))]) (spec/explain-data spec-key field-value)))))
(defn field-valid?[value spec-key req] (defn field-valid?[value spec-key req]
(or (empty? (:form-params req)) (or (empty? (:form-params req))
@ -108,9 +114,10 @@
:mselect multiselect-widget :mselect multiselect-widget
:hidden hidden-widget}) :hidden hidden-widget})
(defn widget-markup (defn widget-markup [values req validate? [id def]]
[id def value req-value validate?] (let [{:keys [label spec widget options to-form]} def
(let [{:keys [label spec widget options]} def value ((or to-form identity) (id values))
req-value (get-in req [:params id])
widget (cond (some? widget) widget widget (cond (some? widget) widget
(some? options) :select (some? options) :select
:else :input) :else :input)
@ -122,28 +129,35 @@
(hform/label id label)) (hform/label id label))
(renderer id def (if validate? req-value value))))) (renderer id def (if validate? req-value value)))))
(defn form-hash [def values] (defn form-level-errors [form-def req]
(str (hash [def values]))) (for [prob (mapcat #(:clojure.spec.alpha/problems
(spec/explain-data % req))
(:form-specs form-def))]
(err-msg [:p "The form must comply to "
(:pred prob)])))
(defn form-hash [form-def values]
(str (hash [form-def values])))
(defn validate? [form-def values req]
(= (form-hash form-def values)
(get-in req [:params :__form-hash])))
(defn render-widgets [form-def values req] (defn render-widgets [form-def values req]
(when-not (spec/valid? ::form form-def) (when-not (spec/valid? ::form form-def)
(throw (ex-info "Form def fails spec" (spec/explain-data ::form form-def)))) (throw (ex-info "Form def fails spec" (spec/explain-data ::form form-def))))
(let [form-hash (form-hash form-def values) (let [validate? (validate? form-def values req)
submitted-hash (get-in req [:params :__form-hash]) form-errors (when validate? (form-level-errors form-def req))
validate? (= form-hash submitted-hash)] all-widgets (conj (map
(conj (map (partial widget-markup
(fn [[field-id field-def]] values req validate?)
(let [{:keys [options to-form]} field-def
value (field-id values)
req-value (get-in req [:params field-id])]
(widget-markup field-id
field-def
(if to-form (to-form value) value)
req-value
validate?)))
(:fields form-def)) (:fields form-def))
(hform/hidden-field "__form-hash" form-hash) (hform/hidden-field "__form-hash"
(anti-forgery-field)))) (form-hash form-def values))
(anti-forgery-field))]
(if form-errors
(concat form-errors all-widgets)
all-widgets)))
(defn form-data [form-def req] (defn form-data [form-def req]
(when-not (spec/valid? ::form form-def) (when-not (spec/valid? ::form form-def)

@ -258,3 +258,29 @@
(nthrest (nthrest
(render-widgets def {:foo "abc"} {:params {:foo "def"}}) (render-widgets def {:foo "abc"} {:params {:foo "def"}})
2))))))) 2)))))))
(deftest form-specs-is-applied
(testing "valid spec"
(let [def {:fields {}
:form-specs [(fn [vals] true)]}]
(is (= [:input {:type "hidden"
:name "__form-hash"
:id "__form-hash"
:value (form-hash def {})}]
(second
(render-widgets
def
{}
{:params {:__form-hash (form-hash def {})}}))))))
(testing "invalid spec"
(let [def {:fields {}
:form-specs [(fn [vals] false)]}]
(is (= [:section.flash--error
[:h2.flash__heading--error "Warning"]
[:p "The form must comply to "
:clojure.spec.alpha/unknown]]
(first
(render-widgets
def
{}
{:params {:__form-hash (form-hash def {})}})))))))

Loading…
Cancel
Save