🚧 More work on forms refactoring.

This commit is contained in:
Andrey Antukh 2019-08-28 20:11:41 +02:00
parent ae2d8330ca
commit 04a5038ff4
7 changed files with 161 additions and 192 deletions

View file

@ -7,11 +7,10 @@
(ns uxbox.main.data.users (ns uxbox.main.data.users
(:require (:require
[beicon.core :as rx] [beicon.core :as rx]
[cljs.spec.alpha :as s] [struct.core :as s]
[struct.core :as stt]
[potok.core :as ptk] [potok.core :as ptk]
[uxbox.main.repo :as rp] [uxbox.main.repo :as rp]
[uxbox.util.i18n :as i18n :refer (tr)] [uxbox.util.i18n :as i18n :refer [tr]]
[uxbox.util.messages :as uum] [uxbox.util.messages :as uum]
[uxbox.util.spec :as us] [uxbox.util.spec :as us]
[uxbox.util.storage :refer [storage]])) [uxbox.util.storage :refer [storage]]))
@ -59,15 +58,15 @@
;; --- Update Profile ;; --- Update Profile
(stt/defs update-profile-spec (s/defs update-profile-spec
{:fullname [stt/required stt/string] {:fullname [s/required s/string]
:email [stt/required stt/email] :email [s/required s/email]
:username [stt/required stt/string] :username [s/required s/string]
:language [stt/required stt/string]}) :language [s/required s/string]})
(defn update-profile (defn update-profile
[data {:keys [on-success on-error]}] [data {:keys [on-success on-error]}]
{:pre [(stt/valid? update-profile-spec data) {:pre [(s/valid? update-profile-spec data)
(fn? on-error) (fn? on-error)
(fn? on-success)]} (fn? on-success)]}
(reify (reify
@ -90,33 +89,28 @@
;; --- Update Password (Form) ;; --- Update Password (Form)
(deftype UpdatePassword [data on-success on-error] (s/defs update-password-spec
ptk/WatchEvent {:password-1 [s/required s/string]
(watch [_ state s] :password-2 [s/required s/string [s/identical-to :password-1]]
(let [params {:old-password (:password-old data) :password-old [s/required s/string]})
:password (:password-1 data)}]
(->> (rp/req :update/profile-password params)
(rx/catch rp/client-error? (fn [e]
(on-error (:payload e))
(rx/empty)))
(rx/do on-success)
(rx/ignore)))))
(s/def ::password-1 string?)
(s/def ::password-2 string?)
(s/def ::password-old string?)
(s/def ::update-password
(s/keys :req-un [::password-1
::password-2
::password-old]))
(defn update-password (defn update-password
[data & {:keys [on-success on-error]}] [data {:keys [on-success on-error]}]
{:pre [(us/valid? ::update-password data) {:pre [(s/valid? update-password-spec data)
(fn? on-success) (fn? on-success)
(fn? on-error)]} (fn? on-error)]}
(UpdatePassword. data on-success on-error)) (reify
ptk/WatchEvent
(watch [_ state s]
(let [params {:old-password (:password-old data)
:password (:password-1 data)}]
(->> (rp/req :update/profile-password params)
(rx/catch rp/client-error? (fn [e]
(on-error (:payload e))
(rx/empty)))
(rx/do on-success)
(rx/ignore))))))
;; --- Update Photo ;; --- Update Photo

View file

@ -8,6 +8,7 @@
(ns uxbox.main.ui.auth.login (ns uxbox.main.ui.auth.login
(:require (:require
[rumext.alpha :as mf] [rumext.alpha :as mf]
[struct.core :as s]
[uxbox.builtins.icons :as i] [uxbox.builtins.icons :as i]
[uxbox.config :as cfg] [uxbox.config :as cfg]
[uxbox.main.data.auth :as da] [uxbox.main.data.auth :as da]
@ -18,9 +19,9 @@
[uxbox.util.i18n :refer [tr]] [uxbox.util.i18n :refer [tr]]
[uxbox.util.router :as rt])) [uxbox.util.router :as rt]))
(def login-form-spec (s/defs login-form-spec
{:username [fm/required fm/string fm/non-empty-string] {:username [fm/required fm/string]
:password [fm/required fm/string fm/non-empty-string]}) :password [fm/required fm/string]})
(defn- on-submit (defn- on-submit
[event form] [event form]
@ -41,8 +42,7 @@
(mf/defc login-form (mf/defc login-form
[] []
(let [{:keys [data] :as form} (fm/use-form {:initial {} (let [{:keys [data] :as form} (fm/use-form {:initial {} :spec login-form-spec})]
:spec login-form-spec})]
[:form {:on-submit #(on-submit % form)} [:form {:on-submit #(on-submit % form)}
[:div.login-content [:div.login-content
(when cfg/isdemo (when cfg/isdemo
@ -52,25 +52,26 @@
{:name "username" {:name "username"
:tab-index "2" :tab-index "2"
:value (:username data "") :value (:username data "")
:on-blur (fm/on-input-blur form) :on-blur (fm/on-input-blur form :username)
:on-change (fm/on-input-change form) :on-change (fm/on-input-change form :username)
:placeholder (tr "auth.email-or-username") :placeholder (tr "auth.email-or-username")
:type "text"}] :type "text"}]
[:input.input-text [:input.input-text
{:name "password" {:name "password"
:tab-index "3" :tab-index "3"
:value (:password data "") :value (:password data "")
:on-blur (fm/on-input-blur form) :on-blur (fm/on-input-blur form :password)
:on-change (fm/on-input-change form) :on-change (fm/on-input-change form :password)
:placeholder (tr "auth.password") :placeholder (tr "auth.password")
:type "password"}] :type "password"}]
[:input.btn-primary [:input.btn-primary
{:name "login" {:name "login"
:tab-index "4" :tab-index "4"
:class (when (:errors form) "btn-disabled") :class (when-not (:valid form) "btn-disabled")
:disabled (boolean (:errors form)) :disabled (not (:valid form))
:value (tr "auth.signin") :value (tr "auth.signin")
:type "submit"}] :type "submit"}]
[:div.login-links [:div.login-links
[:a {:on-click #(st/emit! (rt/nav :auth/recovery-request)) [:a {:on-click #(st/emit! (rt/nav :auth/recovery-request))
:tab-index "5"} :tab-index "5"}

View file

@ -18,7 +18,7 @@
[uxbox.util.i18n :as t :refer [tr]])) [uxbox.util.i18n :as t :refer [tr]]))
(def project-form-spec (def project-form-spec
{:name [fm/required fm/string fm/non-empty-string] {:name [fm/required fm/string]
:width [fm/required fm/number-str] :width [fm/required fm/number-str]
:height [fm/required fm/number-str]}) :height [fm/required fm/number-str]})
@ -51,8 +51,8 @@
:type "text" :type "text"
:name "name" :name "name"
:value (:name data) :value (:name data)
:on-blur (fm/on-input-blur form) :on-blur (fm/on-input-blur form :name)
:on-change (fm/on-input-change form) :on-change (fm/on-input-change form :name)
:auto-focus true}] :auto-focus true}]
[:div.project-size [:div.project-size
[:div.input-element.pixels [:div.input-element.pixels
@ -63,8 +63,8 @@
:type "number" :type "number"
:min 0 :min 0
:max 5000 :max 5000
:on-blur (fm/on-input-blur form) :on-blur (fm/on-input-blur form :width)
:on-change (fm/on-input-change form) :on-change (fm/on-input-change form :width)
:value (:width data)}]] :value (:width data)}]]
[:a.toggle-layout {:on-click #(swap-size % form)} i/toggle] [:a.toggle-layout {:on-click #(swap-size % form)} i/toggle]
[:div.input-element.pixels [:div.input-element.pixels
@ -75,8 +75,8 @@
:name "height" :name "height"
:min 0 :min 0
:max 5000 :max 5000
:on-blur (fm/on-input-blur form) :on-blur (fm/on-input-blur form :height)
:on-change (fm/on-input-change form) :on-change (fm/on-input-change form :height)
:value (:height data)}]]] :value (:height data)}]]]
;; Submit ;; Submit

View file

@ -2,15 +2,13 @@
;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;; ;;
;; Copyright (c) 2016-2017 Andrey Antukh <niwi@niwi.nz> ;; Copyright (c) 2016-2019 Andrey Antukh <niwi@niwi.nz>
;; Copyright (c) 2016-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com> ;; Copyright (c) 2016-2019 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.ui.settings.password (ns uxbox.main.ui.settings.password
(:require (:require
[cuerdas.core :as str]
[lentes.core :as l]
[rumext.alpha :as mf] [rumext.alpha :as mf]
[struct.core :as stt] [struct.core :as s]
[uxbox.builtins.icons :as i] [uxbox.builtins.icons :as i]
[uxbox.main.data.users :as udu] [uxbox.main.data.users :as udu]
[uxbox.main.store :as st] [uxbox.main.store :as st]
@ -19,79 +17,65 @@
[uxbox.util.i18n :refer [tr]] [uxbox.util.i18n :refer [tr]]
[uxbox.util.messages :as um])) [uxbox.util.messages :as um]))
(stt/defs password-form-spec
{:password-1 [stt/required stt/string]
:password-2 [stt/required stt/string]
:password-old [stt/required stt/string]})
(defn- on-submit (defn- on-submit
[event form] [event form]
(dom/prevent-default event) (letfn [(on-error [error]
(prn "on-submit" form) (case (:code error)
#_(let [data (:clean-data form) :uxbox.services.users/old-password-not-match
opts {:on-success #(prn "On Success" %) (swap! form assoc-in [:errors :password-old]
:on-error #(on-error % form)}] {:type ::api :message "settings.password.wrong-old-password"})
(st/emit! (udu/update-profile data opts))))
:else (throw (ex-info "unexpected" {:error error}))))
(on-success [_]
(st/emit! (um/info (tr "settings.password.password-saved"))))]
(dom/prevent-default event)
(let [data (:clean-data form)
opts {:on-success on-success
:on-error on-error}]
(st/emit! (udu/update-password data opts)))))
;; #_(let [data (mx/deref form-data) (s/defs password-form-spec
;; errors (mx/react form-errors) {:password-1 [s/required s/string]
;; valid? (fm/valid? ::password-form data)] :password-2 [s/required s/string [s/identical-to :password-1]]
;; (letfn [(on-change [field event] :password-old [s/required s/string]})
;; (let [value (dom/event->value event)]
;; (st/emit! (assoc-value field value))))
;; (on-success []
;; (st/emit! (um/info (tr "settings.password.password-saved"))))
;; (on-error [{:keys [code] :as payload}]
;; (case code
;; :uxbox.services.users/old-password-not-match
;; (st/emit! (assoc-error :password-old (tr "settings.password.wrong-old-password")))
;; :else
;; (throw (ex-info "unexpected" {:error payload}))))
;; (on-submit [event]
;; (st/emit! (udu/update-password data
;; :on-success on-success
;; :on-error on-error)))]
(mf/defc password-form (mf/defc password-form
[props] [props]
(let [{:keys [data] :as form} (fm/use-form {:initial {} :spec password-form-spec})] (let [{:keys [data] :as form} (fm/use-form {:initial {} :spec password-form-spec})]
(prn "password-form" form)
[:form.password-form {:on-submit #(on-submit % form)} [:form.password-form {:on-submit #(on-submit % form)}
[:span.user-settings-label (tr "settings.password.change-password")] [:span.user-settings-label (tr "settings.password.change-password")]
[:input.input-text [:input.input-text
{:type "password" {:type "password"
:name "password-old" :name "password-old"
:class (fm/error-class form :password-old)
:value (:password-old data "") :value (:password-old data "")
:on-blur (fm/on-input-blur form) :class (fm/error-class form :password-old)
:on-change (fm/on-input-change form) :on-blur (fm/on-input-blur form :password-old)
:on-change (fm/on-input-change form :password-old)
:placeholder (tr "settings.password.old-password")}] :placeholder (tr "settings.password.old-password")}]
[:& fm/error-input {:form form :field :password-old}] [:& fm/field-error {:form form :field :password-old}]
[:input.input-text [:input.input-text
{:type "password" {:type "password"
:name "password-1" :name "password-1"
:class (fm/error-class form :password-1)
:value (:password-1 data "") :value (:password-1 data "")
:on-blur (fm/on-input-blur form) :class (fm/error-class form :password-1)
:on-change (fm/on-input-change form) :on-blur (fm/on-input-blur form :password-1)
:on-change (fm/on-input-change form :password-1)
:placeholder (tr "settings.password.new-password")}] :placeholder (tr "settings.password.new-password")}]
[:& fm/error-input {:form form :field :password-1}] [:& fm/field-error {:form form :field :password-1}]
[:input.input-text [:input.input-text
{:type "password" {:type "password"
:name "password-2" :name "password-2"
:class (fm/error-class form :password-2)
:value (:password-2 data "") :value (:password-2 data "")
:on-blur (fm/on-input-blur form) :class (fm/error-class form :password-2)
:on-change (fm/on-input-change form) :on-blur (fm/on-input-blur form :password-2)
:on-change (fm/on-input-change form :password-2)
:placeholder (tr "settings.password.confirm-password")}] :placeholder (tr "settings.password.confirm-password")}]
[:& fm/error-input {:form form :field :password-2}] [:& fm/field-error {:form form :field :password-2}]
[:input.btn-primary [:input.btn-primary
{:type "submit" {:type "submit"
:class (when-not (:valid form) "btn-disabled") :class (when-not (:valid form) "btn-disabled")

View file

@ -7,15 +7,15 @@
(ns uxbox.main.ui.settings.profile (ns uxbox.main.ui.settings.profile
(:require (:require
[cljs.spec.alpha :as s]
[cuerdas.core :as str] [cuerdas.core :as str]
[lentes.core :as l] [lentes.core :as l]
[rumext.alpha :as mf] [rumext.alpha :as mf]
[struct.core :as s]
[uxbox.builtins.icons :as i] [uxbox.builtins.icons :as i]
[uxbox.main.data.users :as udu] [uxbox.main.data.users :as udu]
[uxbox.main.store :as st] [uxbox.main.store :as st]
[uxbox.util.dom :as dom]
[uxbox.util.data :refer [read-string]] [uxbox.util.data :refer [read-string]]
[uxbox.util.dom :as dom]
[uxbox.util.forms :as fm] [uxbox.util.forms :as fm]
[uxbox.util.i18n :as i18n :refer [tr]] [uxbox.util.i18n :as i18n :refer [tr]]
[uxbox.util.interop :refer [iterable->seq]])) [uxbox.util.interop :refer [iterable->seq]]))
@ -30,23 +30,25 @@
(-> (l/key :profile) (-> (l/key :profile)
(l/derive st/state))) (l/derive st/state)))
(def profile-form-spec (s/defs profile-form-spec
{:fullname [fm/required fm/string fm/non-empty-string] {:fullname [fm/required fm/string]
:username [fm/required fm/string fm/non-empty-string] :username [fm/required fm/string]
:email [fm/required fm/email] :email [fm/required fm/email]
:language [fm/required fm/string]}) :language [fm/required fm/string]})
(defn- on-error (defn- on-error
[error {:keys [errors] :as form}] [error form]
(prn "on-error" error form) (prn "on-error" error form)
(case (:code error) (case (:code error)
:uxbox.services.users/email-already-exists :uxbox.services.users/email-already-exists
(swap! form assoc-in [:errors :email] (swap! form assoc-in [:errors :email]
{:message "errors.api.form.email-already-exists"}) {:type ::api
:message "errors.api.form.email-already-exists"})
:uxbox.services.users/username-already-exists :uxbox.services.users/username-already-exists
(swap! form assoc-in [:errors :username] (swap! form assoc-in [:errors :username]
{:message "errors.api.form.username-already-exists"}))) {:type ::api
:message "errors.api.form.username-already-exists"})))
(defn- initial-data (defn- initial-data
[] []
@ -66,39 +68,53 @@
[props] [props]
(let [{:keys [data] :as form} (fm/use-form {:initial initial-data (let [{:keys [data] :as form} (fm/use-form {:initial initial-data
:spec profile-form-spec})] :spec profile-form-spec})]
(prn "profile-form" form)
[:form.profile-form {:on-submit #(on-submit % form)} [:form.profile-form {:on-submit #(on-submit % form)}
[:span.user-settings-label (tr "settings.profile.section-basic-data")] [:span.user-settings-label (tr "settings.profile.section-basic-data")]
[:input.input-text [:input.input-text
{:type "text" {:type "text"
:name "fullname" :name "fullname"
:on-blur (fm/on-input-blur form) :class (fm/error-class form :fullname)
:on-change (fm/on-input-change form) :on-blur (fm/on-input-blur form :fullname)
:on-change (fm/on-input-change form :fullname)
:value (:fullname data "") :value (:fullname data "")
:placeholder (tr "settings.profile.your-name")}] :placeholder (tr "settings.profile.your-name")}]
[:& fm/error-input {:form form :field :fullname}]
[:& fm/field-error {:form form
:type #{::api}
:field :fullname}]
[:input.input-text [:input.input-text
{:type "text" {:type "text"
:name "username" :name "username"
:on-blur (fm/on-input-blur form) :class (fm/error-class form :username)
:on-change (fm/on-input-change form) :on-blur (fm/on-input-blur form :username)
:on-change (fm/on-input-change form :username)
:value (:username data "") :value (:username data "")
:placeholder (tr "settings.profile.your-username")}] :placeholder (tr "settings.profile.your-username")}]
[:& fm/error-input {:form form :field :username}]
[:& fm/field-error {:form form
:type #{::api}
:field :username}]
[:input.input-text [:input.input-text
{:type "email" {:type "email"
:name "email" :name "email"
:on-blur (fm/on-input-blur form) :class (fm/error-class form :email)
:on-change (fm/on-input-change form) :on-blur (fm/on-input-blur form :email)
:on-change (fm/on-input-change form :email)
:value (:email data "") :value (:email data "")
:placeholder (tr "settings.profile.your-email")}] :placeholder (tr "settings.profile.your-email")}]
[:& fm/error-input {:form form :field :email}]
[:& fm/field-error {:form form
:type #{::api}
:field :email}]
[:span.user-settings-label (tr "settings.profile.section-i18n-data")] [:span.user-settings-label (tr "settings.profile.section-i18n-data")]
[:select.input-select {:value (:language data) [:select.input-select {:value (:language data)
:name "language" :name "language"
:on-blur (fm/on-input-blur form) :class (fm/error-class form :language)
:on-change (fm/on-input-change form)} :on-blur (fm/on-input-blur form :language)
:on-change (fm/on-input-change form :language)}
[:option {:value "en"} "English"] [:option {:value "en"} "English"]
[:option {:value "fr"} "Français"]] [:option {:value "fr"} "Français"]]

View file

@ -20,7 +20,7 @@
(def page-form-spec (def page-form-spec
{:id [fm/uuid] {:id [fm/uuid]
:project [fm/uuid] :project [fm/uuid]
:name [fm/required fm/string fm/non-empty-string] :name [fm/required fm/string]
:width [fm/required fm/number-str] :width [fm/required fm/number-str]
:height [fm/required fm/number-str]}) :height [fm/required fm/number-str]})
@ -59,8 +59,8 @@
{:placeholder "Page name" {:placeholder "Page name"
:type "text" :type "text"
:name "name" :name "name"
:on-blur (fm/on-input-blur form) :on-blur (fm/on-input-blur form :name)
:on-change (fm/on-input-change form) :on-change (fm/on-input-change form :name)
:value (:name data) :value (:name data)
:auto-focus true}] :auto-focus true}]
[:div.project-size [:div.project-size
@ -72,8 +72,8 @@
:type "number" :type "number"
:min 0 :min 0
:max 5000 :max 5000
:on-blur (fm/on-input-blur form) :on-blur (fm/on-input-blur form :width)
:on-change (fm/on-input-change form) :on-change (fm/on-input-change form :width)
:value (:width data)}]] :value (:width data)}]]
[:a.toggle-layout {:on-click #(swap-size % form)} i/toggle] [:a.toggle-layout {:on-click #(swap-size % form)} i/toggle]
[:div.input-element.pixels [:div.input-element.pixels
@ -84,8 +84,8 @@
:type "number" :type "number"
:min 0 :min 0
:max 5000 :max 5000
:on-blur (fm/on-input-blur form) :on-blur (fm/on-input-blur form :height)
:on-change (fm/on-input-change form) :on-change (fm/on-input-change form :height)
:value (:height data)}]]] :value (:height data)}]]]
[:input.btn-primary [:input.btn-primary
{:value "Go go go!" {:value "Go go go!"

View file

@ -14,7 +14,7 @@
[potok.core :as ptk] [potok.core :as ptk]
[rumext.alpha :as mf] [rumext.alpha :as mf]
[rumext.core :as mx] [rumext.core :as mx]
[struct.core :as stt] [struct.core :as st]
[uxbox.util.dom :as dom] [uxbox.util.dom :as dom]
[uxbox.util.i18n :refer [tr]])) [uxbox.util.i18n :refer [tr]]))
@ -22,11 +22,11 @@
(defn validate (defn validate
[data spec] [data spec]
(stt/validate data spec)) (st/validate data spec))
(defn valid? (defn valid?
[data spec] [data spec]
(stt/valid? data spec)) (st/valid? data spec))
;; --- Handlers Helpers ;; --- Handlers Helpers
@ -45,17 +45,16 @@
([self f x y more] (update-fn #(apply f % x y more)))))) ([self f x y more] (update-fn #(apply f % x y more))))))
(defn- translate-error-type (defn- translate-error-type
[type] [code]
(case type (case code
::stt/string "errors.should-be-string" ::st/string "errors.form.string"
::stt/number "errors.should-be-number" ::st/number "errors.form.number"
::stt/number-str "errors.should-be-number" ::st/number-str "errors.form.number"
::stt/integer "errors.should-be-integer" ::st/integer "errors.form.integer"
::stt/integer-str "errors.should-be-integer" ::st/integer-str "errors.form.integer"
::stt/required "errors.required" ::st/required "errors.form.required"
::stt/email "errors.should-be-valid-email" ::st/email "errors.form.email"
::stt/uuid "errors.should-be-uuid" ::st/identical-to "errors.form.does-not-match"
::stt/uuid-str "errors.should-be-valid-uuid"
"errors.undefined-error")) "errors.undefined-error"))
(defn- translate-errors (defn- translate-errors
@ -63,7 +62,7 @@
(reduce-kv (fn [acc key val] (reduce-kv (fn [acc key val]
(if (string? (:message val)) (if (string? (:message val))
(assoc acc key val) (assoc acc key val)
(->> (translate-error-type (:type val)) (->> (translate-error-type (:code val))
(assoc val :message) (assoc val :message)
(assoc acc key)))) (assoc acc key))))
{} errors)) {} errors))
@ -73,8 +72,8 @@
(let [[state update-state] (mf/useState {:data (if (fn? initial) (initial) initial) (let [[state update-state] (mf/useState {:data (if (fn? initial) (initial) initial)
:errors {} :errors {}
:touched {}}) :touched {}})
[errors' clean-data] (validate spec (:data state)) [errors clean-data] (validate spec (:data state))
errors (merge (translate-errors errors') errors (merge (translate-errors errors)
(:errors state))] (:errors state))]
(-> (assoc state (-> (assoc state
:errors errors :errors errors
@ -83,10 +82,9 @@
(impl-mutator update-state)))) (impl-mutator update-state))))
(defn on-input-change (defn on-input-change
[{:keys [data] :as form}] [{:keys [data] :as form} field]
(fn [event] (fn [event]
(let [target (dom/get-target event) (let [target (dom/get-target event)
field (keyword (.-name target))
value (dom/get-value target)] value (dom/get-value target)]
(swap! form (fn [state] (swap! form (fn [state]
(-> state (-> state
@ -94,22 +92,28 @@
(update :errors dissoc field))))))) (update :errors dissoc field)))))))
(defn on-input-blur (defn on-input-blur
[{:keys [touched] :as form}] [{:keys [touched] :as form} field]
(fn [event] (fn [event]
(let [target (dom/get-target event) (let [target (dom/get-target event)]
field (keyword (.-name target))]
(when-not (get touched field) (when-not (get touched field)
(swap! form assoc-in [:touched field] true))))) (swap! form assoc-in [:touched field] true)))))
;; --- Helper Components ;; --- Helper Components
(mf/defc error-input (mf/defc field-error
[{:keys [form field] :as props}] [{:keys [form field type]
:or {only (constantly true)}
:as props}]
(let [touched? (get-in form [:touched field]) (let [touched? (get-in form [:touched field])
error (get-in form [:errors field])] {:keys [message code] :as error} (get-in form [:errors field])]
(when (and touched? error) (when (and touched? error
(cond
(nil? type) true
(ifn? type) (type (:type error))
(keyword? type) (= (:type error) type)
:else false))
[:ul.form-errors [:ul.form-errors
[:li {:key (:type error)} (tr (:message error))]]))) [:li {:key code} (tr message)]])))
(defn error-class (defn error-class
[form field] [form field]
@ -119,50 +123,20 @@
;; --- Additional Validators ;; --- Additional Validators
(def non-empty-string (def string (assoc st/string :message "errors.should-be-string"))
{:message "errors.empty-string" (def number (assoc st/number :message "errors.should-be-number"))
:optional true (def number-str (assoc st/number-str :message "errors.should-be-number"))
:validate #(not (str/empty? %))}) (def integer (assoc st/integer :message "errors.should-be-integer"))
(def integer-str (assoc st/integer-str :message "errors.should-be-integer"))
(def string (assoc stt/string :message "errors.should-be-string")) (def required (assoc st/required :message "errors.required"))
(def number (assoc stt/number :message "errors.should-be-number")) (def email (assoc st/email :message "errors.should-be-valid-email"))
(def number-str (assoc stt/number-str :message "errors.should-be-number")) (def uuid (assoc st/uuid :message "errors.should-be-uuid"))
(def integer (assoc stt/integer :message "errors.should-be-integer")) (def uuid-str (assoc st/uuid-str :message "errors.should-be-valid-uuid"))
(def integer-str (assoc stt/integer-str :message "errors.should-be-integer"))
(def required (assoc stt/required :message "errors.required"))
(def email (assoc stt/email :message "errors.should-be-valid-email"))
(def uuid (assoc stt/uuid :message "errors.should-be-uuid"))
(def uuid-str (assoc stt/uuid-str :message "errors.should-be-valid-uuid"))
;; DEPRECATED ;; DEPRECATED
;; --- Form Validation Api ;; --- Form Validation Api
(defn- interpret-problem
[acc {:keys [path pred val via in] :as problem}]
(cond
(and (empty? path)
(= (first pred) 'contains?))
(let [path (conj path (last pred))]
(update-in acc path assoc :missing))
(and (seq path)
(= 1 (count path)))
(update-in acc path assoc :invalid)
:else acc))
;; (defn validate
;; [spec data]
;; (when-not (s/valid? spec data)
;; (let [report (s/explain-data spec data)]
;; (reduce interpret-problem {} (::s/problems report)))))
;; (defn valid?
;; [spec data]
;; (s/valid? spec data))
;; --- Form Specs and Conformers ;; --- Form Specs and Conformers
(def ^:private email-re (def ^:private email-re