re-frame/src/re_frame/fx.cljs

162 lines
5.9 KiB
Plaintext
Raw Normal View History

(ns re-frame.fx
(:require [reagent.ratom :refer [IReactiveAtom]]
[re-frame.router :refer [dispatch]]
[re-frame.db :refer [app-db]]
2016-07-13 14:32:36 +00:00
[re-frame.events]
[re-frame.loggers :refer [console]]))
;; -- Registration ------------------------------------------------------------
2016-07-13 14:32:36 +00:00
(def ^:private id->handler-fn (atom {}))
(defn lookup-handler
2016-07-13 14:32:36 +00:00
[effect-id]
(get @id->handler-fn effect-id))
2016-07-13 14:32:36 +00:00
(defn clear-all-handlers!
[]
(reset! id->handler-fn {}))
2016-07-13 14:32:36 +00:00
(defn clear-handler!
[effect-id]
(if (lookup-handler effect-id)
(swap! id->handler-fn dissoc effect-id)
(console :warn "re-frame: unable to clear effect handler for " effect-id ". Not defined.")))
2016-07-13 14:32:36 +00:00
(defn register
"register a handler fn for an effect."
[effect-id handler-fn]
(when (lookup-handler effect-id)
(console :warn "re-frame: overwriting an effects handler for: " effect-id)) ;; allow it, but warn.
(swap! id->handler-fn assoc effect-id handler-fn))
;; -- Standard Builtin Effects Handlers --------------------------------------
(defn dispatch-helper
[effect]
(cond
(list? effect) (map dispatch effect)
(vector? effect) (dispatch effect)
:else (console :error "re-frame: expected :dispatch effect to be a list or vector, but got: " effect)))
;; Example:
;; {:dispatch-later {200 [:event-id "param"] ;; in 200ms do this: (dispatch [:event-id "param"])
;; 100 [:also :this :in :100ms]
;; 250 (list [:do ] [:all ] [:three ])}
;;
(register
:dispatch-later
(fn [effect]
(doseq [[ms events] effect]
(js/setTimeout #(dispatch-helper events) ms))))
;; Supply either a vector or a list of vectors. For example:
;;
;; {:dispatch [:event-id "param"] }
;;
;; {:dispatch (list [:do :all] [:three :of] [:these]) }
;;
(register
:dispatch
(fn [val]
(dispatch-helper val)))
2016-07-15 08:46:16 +00:00
;; Provides a way to "forward" events. To put it another way, it provides
;; a way to "sniff" events.
;;
;; Normally, when `(dispatch [:a 42])` happens the event will be routed to
;; the registered handler for `:a`, and that is the end of the matter.
;;
;; BUT with this effect, you can ask that an event is ALSO
;; forwarded to another handler for further processing. This allows this
;; 2nd handler to further process events.
;;
;; You provide a set of events to which you'd like to subscribe.
;; "listen" for a set of events and, when they occur,
;; to "forward" the events to another handler.
;;
;; In effect, if you registered a "listener" for event `:a` and asked for them
;; to be forewared to `[:another "hello"]`, then:
;; - when some did (dispatch [:a 42])
;; - the event would be handler by the normal handler for event `:a`
;; - but then a further dispatch would be made (dispatch [:naother "hello" [:a 42]])
;;
;; As you can see the enter event is "forwarded' to enother handler for further
;; processing.
;;
2016-07-15 04:15:30 +00:00
;; {:forward-events {:register :an-id-for-this-listner
;; :events #{:event1 :event2}
;; :dispatch-to [:eid "eg. param"]} ;; the forwared event will be conj to the end of the dispatch.
;;
2016-07-15 04:15:30 +00:00
;; {:forward-events {:unregister :the-id-supplied-when-registering}}
;;
2016-07-13 14:32:36 +00:00
#_(register
:forward-events
(let [id->listen-fn (atom {})
2016-07-15 04:15:30 +00:00
process-one-entry (fn [{:as m :keys [unlisten listen events dispatch-to]}]
(let [_ (assert (map? m) (str "re-frame: effects handler for :forward-events expected a map or a list of maps. Got: " m))
_ (assert (or (= #{:unlisten} (-> m keys set))
(= #{:listen :events :dispatch-to} (-> m keys set))) "re-frame: effects handler for :forward-events given wrong map keys")]
(if unlisten
(do
(re-frame.core/remove-post-event-callback (@id->listen-fn unlisten))
(swap! id->listen-fn dissoc unlisten))
(let [post-event-callback-fn (fn [event-v _]
(when (events (first event-v))
(dispatch (conj dispatch-to event-v))))]
(re-frame.core/add-post-event-callback post-event-callback-fn)
(swap! id->listen-fn assoc listen post-event-callback-fn)))))]
(fn [val]
(cond
(map? val) (process-one-entry val)
(list? val) (doall (map process-one-entry val))) ;; XXX add else
)))
2016-07-13 14:32:36 +00:00
(register
:deregister-event-handler
(fn [val]
(if (list? val)
(doall (map re-frame.events/clear-handler! val))
(re-frame.events/clear-handler! val))))
(register
:db
(fn [val]
(reset! app-db val)))
;; -- Middleware --------------------------------------------------------------
;; XXX a coeffect for jsDate ?
;; XXX add metadata saying it is fx.
;; XXX add config
;; XXX world or branch ?? Return world?
;; XXX ordering
;; XXX review other standard middleware
;; XXX think about an undo effect
(defn fx
[handler]
(fn fx-handler
[app-db event-vec]
(if-not (satisfies? IReactiveAtom app-db)
(if (map? app-db)
(console :warn "re-frame: Did you use \"fx\" middleware with \"def-event\"? Use \"def-event-fx\" instead (and don't directly use \"fx\")")
(console :warn "re-frame: \"fx\" middleware not given a Ratom. Got: " app-db)))
2016-07-15 08:46:16 +00:00
(let [run-effect (fn [[key val]]
(if-let [effect-fn (lookup-handler key)]
(effect-fn val)
(console :error "re-frame: no effects handler registered for: " key ". Ignoring")))
world {:db @app-db}]
(->> (handler world event-vec) ;; is expected to return a map of effects
(mapv run-effect))))) ;; use mapv to process the returned effects (because it isn't lazy)