This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2024-05-02
Channels
- # announcements (2)
- # babashka (9)
- # calva (8)
- # cider (2)
- # clj-kondo (3)
- # clojure (113)
- # clojure-austin (1)
- # clojure-dev (27)
- # clojure-europe (11)
- # clojure-germany (2)
- # clojure-losangeles (1)
- # clojure-nl (1)
- # clojure-norway (18)
- # clojure-uk (32)
- # clojuredesign-podcast (4)
- # core-async (16)
- # core-typed (45)
- # cursive (5)
- # data-science (1)
- # datomic (7)
- # events (1)
- # gratitude (2)
- # hugsql (1)
- # hyperfiddle (7)
- # integrant (4)
- # keechma (10)
- # leiningen (4)
- # malli (1)
- # missionary (14)
- # off-topic (62)
- # onyx (8)
- # other-languages (21)
- # pathom (1)
- # reitit (4)
- # releases (2)
- # shadow-cljs (35)
- # squint (1)
- # transit (1)
Hi @mihaelkonjevic. How do I invoke a pipeline from a controller?
@UGNMGFJG3 Here is a keechma next pipeline controller from keechma-toolbox https://github.com/keechma/keechma-next-toolbox/blob/master/src/keechma/next/controllers/pipelines.cljs . This is an implementation of a reusable controller which uses pipelines as handlers Here’s one example how you could use it https://github.com/gothinkster/clojurescript-keechma-realworld-example-app/blob/master/src/app/controllers/articles.cljs I would say that 99% percent of our controllers are pipeline controllers, with raw controllers being used for some very specific usecases.
Thanks @mihaelkonjevic!
Hi @mihaelkonjevic. I have this code:
(ns yardwerkz.controllers.cognito-user
(:require
[keechma.next.controller :as ctrl]
[keechma.next.controllers.pipelines :as pipelines]
["aws-amplify/auth" :refer [signOut getCurrentUser]]
[keechma.pipelines.core :as pp :refer [start! stop! invoke] :refer-macros [pipeline!]]
[cljs.core.async :as async :refer-macros [go go-loop]]
[cljs.core.async.interop :refer-macros [<p!]]
[promesa.core :as p]
[taoensso.telemere :as t]
[applied-science.js-interop :as j]
[cljs-bean.core :as cb]
[taoensso.timbre :as log]))
(derive :cognito-user ::pipelines/controller)
(defn get-current-user
[]
(let [deferred (p/deferred)]
(go
(try
(let [response (<p! (getCurrentUser))]
(p/resolve! deferred response))
(catch js/Error _
(p/reject! deferred nil))))
deferred))
(def current-user
(-> (pipeline!
[value {:keys [state*] :as ctrl}]
(t/spy! value)
(get-current-user)
(t/spy! value)
(cb/->clj value)
(pp/reset! state* value)
(rescue! [_]
(pp/reset! state* nil)))
(pp/restartable)))
(def pipelines
{:keechma.on/start current-user})
(defmethod ctrl/prep :cognito-user [ctrl]
(t/event! :ctrl/prep {:data ctrl})
(t/spy! (pipelines/register ctrl pipelines)))
(defmethod ctrl/start :cognito-user [_ _ _ _]
(t/event! :ctrl/start)
nil)
(defmethod ctrl/handle :cognito-user [{:keys [state*]} cmd payload]
(t/event! :ctrl/handler {:data {:state* @state* :cmd cmd :payload payload}})
#_(case cmd
:sign-in
:sign-out ()))
It seems to be executing — except the pipeline is not running. What am I doing wrong?@UGNMGFJG3 It seems to me that ctrl/handle is overridden, pipelines controller has it’s own implementation - this part actually invokes the pipeline (https://github.com/keechma/keechma-next-toolbox/blob/master/src/keechma/next/controllers/pipelines.cljs#L91), so this might be the problem
Ok! Thanks @mihaelkonjevic!
You should be able to leave this implementation as long as you call the handle function yourself after t/event call https://github.com/keechma/keechma-next-toolbox/blob/master/src/keechma/next/controllers/pipelines.cljs#L73 .
Hi @mihaelkonjevic. Is there support for forms with React-Native/Expo?
@UGNMGFJG3 we were using keechma malli forms with React Native, there is nothing specific to web or mobile in this lib. You will need to create your own integration with the UI lib. Here’s an example of how you would implement a web based UI, but the principle is the same for a mobile app
(ns app.frontend.ui.components.inputs
(:require [keechma.next.helix.core :refer [with-keechma use-meta-sub dispatch]]
[keechma.next.helix.lib :refer [defnc]]
[helix.core :as hx :refer [$ <> suspense]]
[helix.dom :as d]
[helix.hooks :as hooks]
["react" :as react]
["react-dom" :as rdom]
[keechma.next.controllers.malli-form.ui :as mfui]))
(defn get-element-props
[default-props props]
(let [element-props (into {} (filter (fn [[k v]] (simple-keyword? k)) props))]
(reduce-kv
(fn [m k v]
(let [prev-v (get k m)
val (cond (and (fn? prev-v) (fn? v))
(fn [& args] (apply prev-v args) (apply v args))
(and (= :class k) (:class m)) (flatten [v (:class m)])
:else v)]
(assoc m k val)))
default-props
element-props)))
(defnc
ErrorsRenderer
[{:keechma.form/keys [controller], :input/keys [attr], :as props}]
(when-let [errors (mfui/use-get-in-errors props controller attr)]
(d/ul {:class "error-messages"}
(map-indexed
(fn [i e] (d/li {:key i} e))
errors))))
(def Errors (with-keechma ErrorsRenderer))
(defnc
TextAreaRenderer
[{:keechma.form/keys [controller], :input/keys [attr], :as props}]
(let [element-props (get-element-props {} props)
value (mfui/use-get-in-data props controller attr)]
(d/textarea {:value (str value)
:onChange #(mfui/on-partial-change props controller attr (.. % -target -value))
:onBlur #(mfui/on-commit-change props controller attr)
& element-props})))
(def TextArea (with-keechma TextAreaRenderer))
(defnc
TextInputRenderer
[{:keechma.form/keys [controller], :input/keys [attr], :as props}]
(let [element-props (get-element-props {} props)
value (mfui/use-get-in-data props controller attr)]
(d/input {:value (str value)
:onChange #(mfui/on-partial-change props controller attr (.. % -target -value))
:onBlur #(mfui/on-commit-change props controller attr)
& element-props})))
(def TextInput (with-keechma TextInputRenderer))
(defmulti input (fn [props] (:input/type props)))
(defmethod input :text [props] ($ TextInput {& props}))
(defmethod input :textarea [props] ($ TextArea {& props}))
(defmulti wrapped-input (fn [props] (:input/type props)))
(defmethod wrapped-input :default [props] (input props))
(defmethod wrapped-input :text
[props]
(d/fieldset {:class "form-group"}
(input (assoc props :class "form-control form-control-lg"))
($ Errors {& props})))
(defmethod wrapped-input :textarea
[props]
(d/fieldset {:class "form-group"}
(input (assoc props :class "form-control form-control-lg"))
($ Errors {& props})))
And here’s an example of a form controller
(ns app.frontend.controllers.user.comment-form
(:require [keechma.next.controller :as ctrl]
[keechma.next.controllers.pipelines :as pipelines]
[keechma.pipelines.core :as pp :refer-macros [pipeline!]]
[keechma.malli-forms.core :as mf]
[keechma.next.controllers.malli-form :as mfc]
[com.verybigthings.funicular.controller :refer [command query get-command get-query req!]]
[app.schema :as schema]))
(derive :user/comment-form ::pipelines/controller)
(def form (mf/make-form schema/registry :app.input.comment/create nil))
(defn mount-form! [{:keys [meta-state* deps-state*]}]
(let [slug (get-in @deps-state* [:router :slug])]
(pp/swap! meta-state* mfc/init-form form {:articles/slug slug})))
(def pipelines
(merge
mfc/pipelines
{:keechma.on/start (pipeline! [value ctrl]
(mount-form! ctrl))
:on-submit
(-> (pipeline! [value {:keys [deps-state* meta-state*], :as ctrl}]
(pp/swap! meta-state* dissoc :submit-errors)
(let [jwt (:jwt @deps-state*)]
(req! ctrl (command :api.comment/create (assoc value :app/jwt jwt))))
(mount-form! ctrl)
(rescue! [error]
(pp/swap! meta-state* assoc :submit-errors error)))
pp/use-existing
pp/dropping
mfc/wrap-submit)}))
(defmethod ctrl/prep :user/comment-form
[ctrl]
(pipelines/register ctrl pipelines))
And a component rendering the form:
(ns app.frontend.ui.components.comment-form
(:require [keechma.next.helix.core :refer
[with-keechma dispatch call use-sub use-meta-sub]]
[keechma.next.helix.lib :refer [defnc]]
[helix.core :as hx :refer [$ <> suspense]]
[helix.dom :as d]
["react" :as react]
["react-dom" :as rdom]
[app.frontend.ui.components.inputs :refer [wrapped-input]]
[keechma.next.controllers.router :as router]
[clojure.string :as str]
[app.frontend.ui.components.form-errors :refer [FormErrors]]))
(defnc
CommentFormRenderer
[props]
(let [current-user (use-sub props :current-user)]
(<>
($ FormErrors {:controller :comment-form})
(d/form
{:class "card comment-form",
:on-submit (fn [e]
(.preventDefault e)
(dispatch props :comment-form :on-submit))}
(d/div {:class "card-block"}
(wrapped-input {:keechma.form/controller :comment-form,
:input/type :textarea,
:input/attr :comments/body,
:rows 3,
:placeholder "Write a comment ..."}))
(d/div {:class "card-footer"}
(d/img {:class "comment-author-img", :src (:ui/avatar current-user)})
(d/button {:class "btn btn-sm btn-primary"} "Post Comment"))))))
(def CommentForm (with-keechma CommentFormRenderer))
UI integration will use hooks from this file https://github.com/keechma/keechma-next-toolbox/blob/master/src/keechma/next/controllers/malli_form/ui.cljs
There are three handlers defined here:
• on-partial-change
- use it for text like inputs where event is triggered on each change, but it’s not “done” until blur event happens
• on-commit-change
- call this one to commit the partial changes
• on-atomic-change
- use this for elements like select / checkbox / radio where every change is immediately “done”
These are used to implement the validation UX as per principles outlined in this blog post https://github.com/keechma/keechma-next-toolbox/blob/master/src/keechma/next/controllers/malli_form/ui.cljs