Form builder as a way to simplify some things

This commit is contained in:
2022-07-14 22:08:03 -07:00
parent 99fe24ddae
commit 586978823e
6 changed files with 760 additions and 640 deletions

View File

@@ -17,15 +17,7 @@
:data
(get-in f))))
(re-frame/reg-sub
::is-loading?
(fn [db [_ x]]
(if (#{"loading" :loading} (get-in db [::forms x :status]) )
true
false)))
(re-frame/reg-sub
(re-frame/reg-sub
::loading-class
(fn [db [_ x]]
(if (#{"loading" :loading} (get-in db [::forms x :status]) )
@@ -43,10 +35,6 @@
:data data
:complete-listener complete-listener})))
(defn ^:depracated saved-form [db form data]
(update-in db [::forms form]
assoc :error nil :status nil :data data))
(defn triggers-saved [form data-key]
(i/->interceptor
:id :triggers-saved
@@ -55,7 +43,6 @@
:after (fn [context]
(let [db (i/get-coeffect context :db)
result (get-in (i/get-coeffect context :event) [1 data-key])]
(cond-> context
true
(i/assoc-effect :db (update-in db

View File

@@ -0,0 +1,106 @@
(ns auto-ap.forms.builder
(:require
[auto-ap.views.utils :refer [bind-field]]
[re-frame.core :as re-frame]
[react :as react]
[reagent.core :as r]
[auto-ap.forms :as forms]
[auto-ap.status :as status]))
(defonce ^js/React.Context form-context (react/createContext "default"))
(def ^js/React.Provider Provider (. form-context -Provider))
(def ^js/React.Consumer Consumer (. form-context -Consumer))
(defonce ^js/React.Context form-scope-context (react/createContext []))
(def ^js/React.Provider FormScopeProvider (. form-scope-context -Provider))
(def ^js/React.Consumer FormScopeConsumer (. form-scope-context -Consumer))
(defn builder [{:keys [can-submit data-sub change-event submit-event id fullwidth?] :as z}]
(let [data-sub (or data-sub [::forms/form id])
change-event (or change-event [::forms/change id])
{:keys [data error]} @(re-frame/subscribe data-sub)]
(r/create-element Provider #js {:value #js {:can-submit @(re-frame/subscribe can-submit)
:change-event change-event
:submit-event submit-event
:error error
:status @(re-frame/subscribe [::status/single id])
:id id
:data data
:fullwidth? fullwidth?}}
(r/as-element
(into [:form {:on-submit (fn [e]
(when (.-stopPropagation e)
(.stopPropagation e)
(.preventDefault e))
(when can-submit
(re-frame/dispatch-sync (vec (conj submit-event {})))))}]
(r/children (r/current-component)))))))
(defn raw-field []
(let [[child] (r/children (r/current-component))]
[:> Consumer {}
(fn [consume-form]
(r/as-element
[:> FormScopeConsumer {}
(fn [form-scope]
(r/as-element
[bind-field (-> child
(update-in [1 :field] (fn [f]
(cond
(sequential? f)
(into form-scope f)
f
(conj form-scope f)
:else
nil)))
(assoc-in [1 :subscription] (aget consume-form "data"))
(assoc-in [1 :event] (aget consume-form "change-event")))]))]))]))
(defn with-scope [{:keys [scope]}]
(r/create-element FormScopeProvider #js {:value scope}
(r/as-element (into [:<>]
(r/children (r/current-component))))))
(defn field []
(let [[label child] (r/children (r/current-component))]
[:> Consumer {}
(fn [consume]
(r/as-element
[:div.field
(when label (if (aget consume "fullwidth?") [:p.help label]
[:label.label label]))
[:div.control [raw-field {} child]]]))]))
(defn section [{:keys [title]}]
[:<>
[:h4.is-4.title title]
[:hr]
(into [:div {:style {:margin-bottom "5em"}}]
(r/children (r/current-component)))])
(defn submit-button []
(let [[child] (r/children (r/current-component))]
[:> Consumer {}
(fn [consume]
(let [status (aget consume "status")
can-submit (aget consume "can-submit")
fullwidth? (aget consume "fullwidth?")]
(r/as-element
[:button.button.is-medium.is-primary {:disabled (or (status/disabled-for status)
(not can-submit))
:class (cond-> (status/class-for status)
fullwidth? (conj "is-fullwidth")) }
child])))]))
(defn error-notification []
(let [[child] (r/children (r/current-component))]
[:> Consumer {}
(fn [consume]
(r/as-element
(when-let [error (aget consume "error")]
^{:key error}
[:div.has-text-danger.animated.fadeInUp {} error])))]))

View File

@@ -1,6 +1,7 @@
(ns auto-ap.views.components.address
(:require [auto-ap.entities.address :as address]
[auto-ap.views.utils :refer [dispatch-value-change dispatch-event bind-field horizontal-field]]))
[auto-ap.views.utils :refer [dispatch-value-change dispatch-event bind-field horizontal-field]]
[auto-ap.forms.builder :as form-builder]))
(defn address-field [{:keys [event field subscription]}]
[:span
@@ -58,3 +59,50 @@
:event event
:subscription subscription
:placeholder "95014"}]]]]])
(defn address2-field []
[:span
[horizontal-field
nil
[:div.control
[:p.help "Address"]
[form-builder/raw-field
[:input.input.is-expanded {:type "text"
:placeholder "1700 Pennsylvania Ave"
:field [:street1]
:spec ::address/street1}]]]]
[horizontal-field
nil
[:div.control
[form-builder/raw-field
[:input.input.is-expanded {:type "text"
:placeholder "Suite 400"
:field [:street2]
:spec ::address/street2}]]]]
[horizontal-field
nil
[:div.control
[:p.help "City"]
[form-builder/raw-field
[:input.input.is-expanded {:type "text"
:placeholder "Cupertino"
:field [:city]
:spec ::address/city}]]]
[:div.control
[:p.help "State"]
[form-builder/raw-field
[:input.input {:type "text"
:placeholder "CA"
:field [:state]
:spec ::address/state
:size 2
:max-length "2"}]]]
[:div.control
[:p.help "Zip"]
[form-builder/raw-field
[:input.input {:type "text"
:field [:zip]
:spec ::address/zip
:placeholder "95014"}]]]]])

View File

@@ -1,21 +1,20 @@
(ns auto-ap.views.pages.admin.clients
(:require
[auto-ap.forms :as forms]
[auto-ap.subs :as subs]
[auto-ap.routes :as routes]
[auto-ap.status :as status]
[auto-ap.subs :as subs]
[auto-ap.views.components.admin.side-bar :refer [admin-side-bar]]
[auto-ap.views.components.grid :as grid]
[auto-ap.views.components.layouts :refer [side-bar-layout]]
[auto-ap.views.pages.admin.clients.form :as form]
[auto-ap.views.pages.admin.clients.side-bar :as side-bar]
[auto-ap.views.pages.admin.clients.table :as table]
[auto-ap.views.utils :refer [dispatch-event transition switch-transition with-user transition-group]]
[auto-ap.views.pages.page-stack :as page-stack]
[auto-ap.views.utils :refer [with-user]]
[bidi.bidi :as bidi]
[clojure.string :as str]
[re-frame.core :as re-frame]
[reagent.core :as r]
[vimsical.re-frame.fx.track :as track]
[bidi.bidi :as bidi]
[auto-ap.routes :as routes]))
[vimsical.re-frame.fx.track :as track]))
(re-frame/reg-event-db
::received-intuit-bank-accounts
@@ -25,7 +24,7 @@
(re-frame/reg-event-fx
::mounted
[with-user]
(fn [{:keys [db user]} _]
(fn [{:keys [user]} _]
{::track/register {:id ::params
:subscription [::params]
:event-fn (fn [params] [::params-change params])}
@@ -49,14 +48,6 @@
(seq filter-params) (merge filter-params)
(seq table-params) (merge table-params))))
(re-frame/reg-event-db
::new
(fn [db [_ client-id]]
(-> db
(forms/start-form ::form/form {:bank-accounts []}))))
(re-frame/reg-event-fx
::params-change
(fn [_ [_ params]]
@@ -75,125 +66,28 @@
(assoc (grid/virtual-paginate-controls (:start params ) (:per-page params) matching-clients)
:data (grid/virtual-paginate (:start params) (:per-page params) matching-clients)))))
(defn stacked-page [& children]
[:div
(->> children
(reverse)
(filter identity)
first)])
(defn hierachy []
(let [last-stack-size (r/atom (->> (r/current-component)
r/children
(filter identity) count))
forward? (r/atom false)]
(fn []
(let [stack (filter identity (r/children (r/current-component)))
current-stack-size (count stack)
current-child (last stack)]
(when (not= current-stack-size @last-stack-size)
(reset! forward? (> current-stack-size @last-stack-size))
(reset! last-stack-size current-stack-size))
[:div
[switch-transition {:mode "in-out"}
^{:key current-stack-size}
[transition
{:timeout 100
:exit true
:in true #_(= current-stack- (:key (meta child)))
:appear (> current-stack-size 1)}
(clj->js (fn [state]
(r/as-element
[:div {:style {
:position (cond
(= "entered" state)
""
(= "entering" state)
"absolute"
(= "exiting" state)
"absolute"
(= "exited" state)
"")
:transition "opacity 300ms ease-in-out, transform 300ms ease-in-out"
:opacity (cond
(= "entered" state)
""
(= "entering" state)
0.0
(= "exiting" state)
1.0
(= "exited" state)
0.0)
:transform (if @forward?
(cond
(= "entered" state)
""
(= "entering" state)
"translateX(100%)"
(= "exiting" state)
"translateX(-100%)"
(= "exited" state)
"translateX(0%)")
(cond
(= "entered" state)
""
(= "entering" state)
"translateX(-100%)"
(= "exiting" state)
"translateX(100%)"
(= "exited" state)
"translateX(0%)"))}}
current-child])))]]
]))))
(defn test-appear []
[:div
[hierachy
[table/clients-table {:page @(re-frame/subscribe [::page])
:status @(re-frame/subscribe [::status/single ::page])}]
(when (#{:admin-specific-bank-account :admin-specific-client} @(re-frame/subscribe [::subs/active-route]))
[form/new-client-form])
(when (= :admin-specific-bank-account @(re-frame/subscribe [::subs/active-route]))
[:div "This is a bank account!"])]])
(defn breadcrumbs []
[:h1.title.is-1
(cond (= :admin-clients @(re-frame/subscribe [::subs/active-route]))
"Clients"
:else
[:span [:a {:href (bidi/path-for routes/routes :admin-clients)}
"Clients"]
" / "
(or (:name (:data @(re-frame/subscribe [::forms/form ::form/form])))
[:i "New client"])])
])
(def admin-clients-content
(with-meta
(fn []
[:div
[breadcrumbs]
[:div.is-pulled-right
(when (= :admin-clients @(re-frame/subscribe [::subs/active-route]))
[:a.button.is-primary.is-outlined {:href (bidi/path-for routes/routes :admin-specific-client :id "new")} "New client"])]
[test-appear]
])
[page-stack/page-stack
{:active @(re-frame/subscribe [::subs/active-route])
:pages [{:key :admin-clients
:breadcrumb "Clients"
:content [:<>
[:div.is-pulled-right
[:a.button.is-primary.is-outlined {:href (bidi/path-for routes/routes :admin-specific-client :id "new")} "New client"]]
[table/clients-table {:page @(re-frame/subscribe [::page])
:status @(re-frame/subscribe [::status/single ::page])}]]}
{:key :admin-specific-client
:breadcrumb [:span [:a {:href (bidi/path-for routes/routes :admin-clients)}
"Clients"]
" / "
(or (:name @(re-frame/subscribe [::form/client]))
[:i "New client"])]
:content [form/new-client-form]}
]}]])
{:component-did-mount #(re-frame/dispatch [::mounted])
:component-will-unmount #(re-frame/dispatch-sync [::unmounted])}))

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,85 @@
(ns auto-ap.views.pages.page-stack
(:require
[auto-ap.views.utils :refer [switch-transition transition]]
[reagent.core :as r]))
(defn page-stack []
(let [last-stack-size (r/atom (->> (r/current-component)
r/children
(filter identity) count))
forward? (r/atom false)]
(fn [{:keys [pages active]}]
(let [current-stack-size (->> pages
(take-while #(not= (:key %)
active))
count)
current-child (->> pages
(filter #(= (:key %)
active))
first)]
(when (not= current-stack-size @last-stack-size)
(reset! forward? (> current-stack-size @last-stack-size))
(reset! last-stack-size current-stack-size))
[:div
[:h1.title (:breadcrumb current-child)]
[switch-transition {:mode "in-out"}
^{:key current-stack-size}
[transition
{:timeout 100
:exit true
:in true #_(= current-stack- (:key (meta child)))
:appear (> current-stack-size 1)}
(clj->js (fn [state]
(r/as-element
[:div {:style {
:position (cond
(= "entered" state)
""
(= "entering" state)
"absolute"
(= "exiting" state)
"absolute"
(= "exited" state)
"")
:transition "opacity 300ms ease-in-out, transform 300ms ease-in-out"
:opacity (cond
(= "entered" state)
""
(= "entering" state)
0.0
(= "exiting" state)
1.0
(= "exited" state)
0.0)
:transform (if @forward?
(cond
(= "entered" state)
""
(= "entering" state)
"translateX(100%)"
(= "exiting" state)
"translateX(-100%)"
(= "exited" state)
"translateX(0%)")
(cond
(= "entered" state)
""
(= "entering" state)
"translateX(-100%)"
(= "exiting" state)
"translateX(100%)"
(= "exited" state)
"translateX(0%)"))}}
(:content current-child)])))]]]))))