Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Fulcro Validator with custom error strings
(defn make-validator
"Create a form/field validation function using a supplied field checker. The field checker will be given
the entire form (denormalized) and a single field key that is to be checked. It must return
a boolean indicating if that given field is valid or not, or a custom error map if invalid.
During a recursive check for a form, the validation function will be in the correct context (e.g. the form supplied will contain
the field. There is no need to search for it in subforms).
make-validator returns a three arity function:
- `(fn [form] ...)` - Calling this version will return :unchecked, :valid, or :invalid for the entire form.
- `(fn [form field] ...)` - Calling this version will return :unchecked, :valid, or :invalid for the single field.
- `(fn [form field return-map?] ...)` - Calling this version will return :unchecked, :valid, :invalid, or a custom error map for the single field.
Typical usage would be to show messages around the form fields:
```
(defn field-valid? [form field] true) ; just return true
(def my-validator (make-validator field-valid?))
(defn valid? [form field]
(= :valid (my-validator form field)))
(defn checked? [form field]
(not= :unchecked (my-validator form field)))
```
A more complex example with custom error messages:
```
(defn field-valid? [form field]
(case field
:my-field
(let [field-val (get form field)]
(cond
(= field-val :foo)
{:msg \"must not be :foo\"}
(= field-val :bar)
{:msg \":bar is an invalid value\"}
(= field-val :baz)
false
:else
true))))
(def map-validator (make-validator field-valid?))
(defn custom-msg? [form field]
(let [valid? (map-validator form field true)]
(when (map? valid?)
(:msg valid?)))
```
"
[field-valid?]
(fn custom-get-validity*
([ui-entity-props field return-map?]
(let [{{complete? ::fs/complete?} ::fs/config} ui-entity-props
complete? (or complete? #{})]
(if (not (complete? field))
:unchecked
(let [valid? (field-valid? ui-entity-props field)]
(if (or (map? valid?) (not valid?))
(if return-map?
valid?
:invalid)
:valid)))))
([ui-entity-props field]
(custom-get-validity* ui-entity-props field false))
([ui-entity-props]
(let [{{:keys [::fs/fields ::fs/subforms]} ::fs/config} ui-entity-props
immediate-subforms (fs/immediate-subforms ui-entity-props (-> subforms keys set))
field-validity (fn [current-validity k] (fs/merge-validity current-validity (custom-get-validity* ui-entity-props k)))
subform-validities (map custom-get-validity* immediate-subforms)
subform-validity (reduce fs/merge-validity :valid subform-validities)
this-validity (reduce field-validity :valid fields)]
(fs/merge-validity this-validity subform-validity)))))
(s/def ::range number?)
(s/def ::rsd number?)
(s/def ::threshold number?)
(s/def :parameter/precisionCode (s/nilable (s/keys :opt-un [::range ::rsd ::threshold])))
(defn parameter-valid [form field]
(let [v (get form field)]
(case field
:parameter/precisionCode
(let [edn-val (try (cljs.reader/read-string v) (catch js/Object ex nil))
{:keys [threshold range rsd]} edn-val]
(cond
(and (some? threshold) (or (not range) (not rsd)))
{:msg "If Threshold is set, then both Range and RSD must also be set"}
(and (not threshold) (some? range) (some? rsd))
{:msg "If Threshold is not set, then either Range or RSD can be set, but not both"}
(not (s/valid? field edn-val))
{:msg (str "All values must be numbers")}
:else
true)))))
(def validator (make-validator parameter-valid))
(defsc my-comp [this props]
{}
(div :.field
(dom/label {} "Precision")
(let [valid? (validator props :parameter/precisionCode true)]
(when (map? valid?)
(div (:msg valid?))))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment