reagent-project / reagent-forms

Bootstrap form components for Reagent
339 stars 78 forks source link

Nested conditional forms? #136

Closed rasensio1 closed 6 years ago

rasensio1 commented 6 years ago

I ran into a stumbling block when trying to implement a dashboard with various forms based on a :mode attribute in my app-state atom.

I'm not sure if reagent-forms is built to support something like this, or if I should just go vanilla.

My example UI flow:

1) A user is shown a form with modes 1 and 2. 2) A user selects mode 1 (of #{1 2}). This shows form 1. 3) Form 1 shows sub-options (#{a b c}) and the user selects option "a". This shows form "1.a"

So there is a separate form for each 1a 1b 1c, 2d, 2e, 2f... based on some settings in the atom, and the form shown should be updated on changes to the atom.

My idea in code is something like the following, though it doesn't work:

(defn dash [ratom]  ;; Main form (1 or 2)
  (fn []
    [:div [:h3 "Mode"]
     [bind-fields mode-btns ratom]
     (if (mode-one? @ratom)
       [bind-fields (mode-one-opts @ratom) ratom])]))

(def mode-btns ;; Main form choices
  [:div
   [:div.btn-group {:field :single-select :id :mode}
    [:button.btn.btn-default  {:key {:one true}} "Mode one"]
    [:button.btn.btn-default  {:key {:two true}} "Mode two"]]])

(defn mode-one-opts [state] ;; options a, b, and c.
  [:div
   [:div.btn-group {:field :single-select :id :mode.one}
    [:button.btn.btn-default     {:key {:a true}} "Sub-form a"]
    [:button.btn.btn-default     {:key {:b true}} "Sub-form b"]]
   [:div.sub-options
    (when (mode-one-a? state)
      mode-one-a-opts)] ])

(def mode-one-a-opts ;; form 1.a
  [:div [:h1 "Form A"]
   [:div [:p "Mass"]]
   ;; ... Other inputs...
   [:div    [:input.form-control {:field :numeric :id :build.mass}]]])
yogthos commented 6 years ago

I think you're looking for the container field. You can set the visibility of the container based on the state of the document, e.g:

(def mode-btns
  [:div.btn-group {:field :single-select :id :mode}
   [:button.btn.btn-default  {:key :one} "Mode one"]
   [:button.btn.btn-default  {:key :two} "Mode two"]])

(def sub-form-btns
  [:div
   [:div
    {:field :container
     :visible? #(= :one (:mode %))}
    [:div.btn-group {:field :single-select :id :sub-form}
     [:button.btn.btn-default {:key :a} "Sub-form a"]
     [:button.btn.btn-default {:key :b} "Sub-form b"]]]
   [:div
    {:field :container
     :visible? #(= :two (:mode %))}
    [:div.btn-group {:field :single-select :id :sub-form}
     [:button.btn.btn-default {:key :c} "Sub-form c"]
     [:button.btn.btn-default {:key :d} "Sub-form d"]]]])

(def form
  [:div
   mode-btns
   [:div
    {:field :container
     :visible? #(= :one (:mode %))}
    sub-form-btns]
   [:div.sub-options
    {:field :container
     :visible? #(= :a (:sub-form %))}
    [:h1 "Form A"]
    [:div [:p "Mass"]]
    [:div    [:input.form-control {:field :numeric :id :build.mass}]]]])

(defn dash [ratom]  ;; Main form (1 or 2)
  (r/with-let [doc (r/atom {})]
    [:div
     [:p (str @doc)]
     [bind-fields form doc]]))

The main thing to be aware of is that the form has to be a Hiccup template, and can't contain component functions. The reason being that reagent-form walks the form definition and looks for field definitions within it.

Another option is to track the state of the atom externally as described here. With this approach you can create multiple bind-fields definitions and control overall view state externally.

rasensio1 commented 6 years ago

Perfect! Yeah, this is what I was looking for. I see now, of course that this is all in the docs... Anyways, thank you very much.