Version 0.8.0-alpha10
This commit is contained in:
parent
f78541af59
commit
c8659d6770
|
@ -3,7 +3,7 @@
|
|||
[cljs.spec :as s]))
|
||||
|
||||
|
||||
;; -- Spec -----------------------------------------------------------------
|
||||
;; -- Spec --------------------------------------------------------------------
|
||||
;;
|
||||
;; This is a clojure.spec specification which documents the structure of app-db
|
||||
;; See: http://clojure.org/guides/spec
|
||||
|
@ -52,12 +52,12 @@
|
|||
;; But we are not to load the setting for the "showing" filter. Just the todos.
|
||||
;;
|
||||
|
||||
(def lsk "todos-reframe") ;; localstore key
|
||||
(def ls-key "todos-reframe") ;; localstore key
|
||||
|
||||
(defn localstore->todos
|
||||
"Read in todos from LS, and process into a map we can merge into app-db."
|
||||
"Read in todos from localstore, and process into a map we can merge into app-db."
|
||||
[]
|
||||
(some->> (.getItem js/localStorage lsk)
|
||||
(some->> (.getItem js/localStorage ls-key)
|
||||
(cljs.reader/read-string) ;; stored as an EDN map.
|
||||
(into (sorted-map)) ;; map -> sorted-map
|
||||
(hash-map :todos))) ;; access via the :todos key
|
||||
|
@ -65,5 +65,5 @@
|
|||
(defn todos->local-store
|
||||
"Puts todos into localStorage"
|
||||
[todos]
|
||||
(.setItem js/localStorage lsk (str todos))) ;; sorted-map writen as an EDN map
|
||||
(.setItem js/localStorage ls-key (str todos))) ;; sorted-map writen as an EDN map
|
||||
|
||||
|
|
|
@ -14,8 +14,7 @@
|
|||
"throw an exception if db doesn't match the spec."
|
||||
[a-spec db]
|
||||
(when-not (s/valid? a-spec db)
|
||||
(throw (ex-info "spec check failed: " {:problems
|
||||
(s/explain-str a-spec db)}))))
|
||||
(throw (ex-info (str "spec check failed: " (s/explain-str a-spec db)) {}))))
|
||||
|
||||
;; Event handlers change state, that's their job. But what happens if there's
|
||||
;; a bug which corrupts app state in some subtle way? This interceptor is run after
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
(defproject re-frame "0.8.0-SNAPSHOT"
|
||||
(defproject re-frame "0.8.0"
|
||||
:description "A Clojurescript MVC-like Framework For Writing SPAs Using Reagent."
|
||||
:url "https://github.com/Day8/re-frame.git"
|
||||
:license {:name "MIT"}
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
(ns re-frame.cofx
|
||||
(:require
|
||||
[re-frame.db :refer [app-db]]
|
||||
[re-frame.interceptor :refer [->interceptor]]
|
||||
[re-frame.registrar :refer [get-handler clear-handlers register-handler]]
|
||||
[re-frame.loggers :refer [console]]))
|
||||
|
||||
|
||||
;; -- Registration ------------------------------------------------------------
|
||||
|
||||
(def kind :cofx)
|
||||
(assert (re-frame.registrar/kinds kind))
|
||||
(def register (partial register-handler kind))
|
||||
|
||||
;; -- Interceptor -------------------------------------------------------------
|
||||
|
||||
(defn coeffect
|
||||
"An interceptor which adds to a `context's` `:coeffects`.
|
||||
|
||||
`coeffects` are the input resources required by an event handler
|
||||
to perform its job. The two most obvious ones are `db` and `event`.
|
||||
But sometimes a handler might need other resources.
|
||||
|
||||
Perhaps a handler needs a random number or a GUID or the current datetime.
|
||||
Perhaps it needs access to an in-memory DataScript database.
|
||||
|
||||
If the handler directly access these resources, it stops being as
|
||||
pure. It immedaitely becomes harder to test, etc.
|
||||
|
||||
So the necessary resources are \"injected\" into the `coeffect` (map)
|
||||
given the handler.
|
||||
|
||||
Given one or more `ids`, this function will iterately lookup the
|
||||
registered coeffect handlers (via `reg-cofx`) and call each of them
|
||||
giving the current `:coeffect` as an argument, and expecting a
|
||||
modified coeffect to be returned.
|
||||
"
|
||||
;; Why? We want our handlers to be pure. If a handler calls `js/Date.` then
|
||||
;; it stops being as pure. It is harder to test.
|
||||
;;
|
||||
;; And what if a handler needs a random number? Or a GUID? These kinds of input resources are
|
||||
;; termed `coeffects` (sometimes called side-causes).
|
||||
[& ids]
|
||||
(->interceptor
|
||||
:name :coeffects
|
||||
:before (fn coeffects-before
|
||||
[context]
|
||||
(let [orig-coeffect (:coeffects context)
|
||||
run-id (fn [coeffect id]
|
||||
((get-handler kind id) coeffect))
|
||||
new-coeffect (reduce run-id orig-coeffect ids)]
|
||||
(assoc context :coeffects new-coeffect)))))
|
||||
|
||||
|
||||
;; -- Standard Builtin CoEffects Handlers --------------------------------------------------------
|
||||
|
||||
;; :db
|
||||
;;
|
||||
;; Adds to coeffects the value in `app-db`, under the key `:db`
|
||||
(register
|
||||
:db
|
||||
(fn db-coeffects-handler
|
||||
[coeffects]
|
||||
(assoc coeffects :db @app-db)))
|
||||
|
||||
|
||||
;; this interceptor is so commonly used that we reify it
|
||||
(def add-db (coeffect :db))
|
||||
|
||||
|
||||
;; XXX what about a coeffect which reads LocalStore. Use in todomvc example.
|
||||
|
||||
;; -- Example ------------------------------------------------------------------------------------
|
||||
|
||||
;; An example coeffect handler, which adds the current datetime under
|
||||
;; the `:now` key.
|
||||
;;
|
||||
;; Example Usage:
|
||||
;; (reg-event-fx ;; `-fx` variant used in registration
|
||||
;; :some-id
|
||||
;; [(coeffect :now) debug] ;; notice (coeffect :now) is in interceptors
|
||||
;; (fn [coeffect event] ;; -fx handlers are given the entire coeffect as 1st argument
|
||||
;; (let [dt (:now coeffect)] ;; `:now` is available
|
||||
;; ...)))
|
||||
;;
|
||||
;; As a further exercise, consider how you would write a `random` interceptor which adds a random
|
||||
;; number into a handler's coeffect?
|
||||
#_(register
|
||||
:now
|
||||
(fn now-coeffects-handler
|
||||
[coeffects]
|
||||
(assoc coeffect :now (js/Date.))))
|
|
@ -1,14 +1,16 @@
|
|||
(ns re-frame.core
|
||||
(:require
|
||||
[re-frame.events :as events]
|
||||
[re-frame.subs :as subs]
|
||||
[re-frame.fx :as fx]
|
||||
[re-frame.router :as router]
|
||||
[re-frame.loggers :as loggers]
|
||||
[re-frame.registrar :as registrar]
|
||||
[re-frame.interceptor :as interceptor :refer [base
|
||||
db-handler->interceptor fx-handler->interceptor
|
||||
ctx-handler->interceptor]]))
|
||||
[re-frame.events :as events]
|
||||
[re-frame.subs :as subs]
|
||||
[re-frame.fx :as fx]
|
||||
[re-frame.cofx :as cofx]
|
||||
[re-frame.router :as router]
|
||||
[re-frame.loggers :as loggers]
|
||||
[re-frame.registrar :as registrar]
|
||||
[re-frame.interceptor :as interceptor]
|
||||
[re-frame.std-interceptors :as std-interceptors :refer [db-handler->interceptor
|
||||
fx-handler->interceptor
|
||||
ctx-handler->interceptor]]))
|
||||
|
||||
|
||||
;; -- dispatch
|
||||
|
@ -16,14 +18,14 @@
|
|||
(def dispatch-sync router/dispatch-sync)
|
||||
|
||||
|
||||
;; XXX move certain API functions up here - to get code completion and docs
|
||||
;; XXX move certain API functions up to this core level - to get code completion and docs
|
||||
;; XXX add a clear all handlers
|
||||
;; XXX add a push handlers for testing purposes
|
||||
;; XXX Add ->interceptor assoc-coeffect etc to API
|
||||
;; XXX for testing purposes, and a way to snapshot re-frame instance. Then re-instate
|
||||
;; XXX on figwheel reload, should invalidate all re-frame subscriptions
|
||||
|
||||
|
||||
;; -- interceptor related
|
||||
;; useful if you are writing your own interceptors
|
||||
(def ->interceptor interceptor/->interceptor)
|
||||
(def enqueue interceptor/enqueue)
|
||||
(def get-coeffect interceptor/get-coeffect)
|
||||
|
@ -33,57 +35,60 @@
|
|||
|
||||
|
||||
;; -- standard interceptors
|
||||
(def debug interceptor/debug)
|
||||
(def path interceptor/path)
|
||||
(def enrich interceptor/enrich)
|
||||
(def trim-v interceptor/trim-v)
|
||||
(def after interceptor/after)
|
||||
(def on-changes interceptor/on-changes)
|
||||
(def debug std-interceptors/debug)
|
||||
(def path std-interceptors/path)
|
||||
(def enrich std-interceptors/enrich)
|
||||
(def trim-v std-interceptors/trim-v)
|
||||
(def after std-interceptors/after)
|
||||
(def on-changes std-interceptors/on-changes)
|
||||
|
||||
|
||||
;; -- subscribe
|
||||
;; -- subscriptions: reading and writing
|
||||
(def reg-sub-raw subs/register-raw)
|
||||
(def reg-sub subs/reg-sub)
|
||||
(def subscribe subs/subscribe)
|
||||
|
||||
;; -- effects
|
||||
;; -- effects
|
||||
(def reg-fx fx/register)
|
||||
(def clear-fx (partial registrar/clear-handlers fx/kind))
|
||||
|
||||
|
||||
|
||||
;; -- coeffects
|
||||
(def reg-cofx cofx/register)
|
||||
(def clear-cofx (partial registrar/clear-handlers cofx/kind))
|
||||
|
||||
;; -- Events
|
||||
|
||||
;; usage (clear-event! :some-id)
|
||||
(def clear-event! (partial registrar/clear-handlers events/kind)) ;; XXX name with !
|
||||
(def clear-all-events! (partial registrar/clear-handlers events/kind)) ;; XXX name with !
|
||||
|
||||
|
||||
(defn reg-event-db
|
||||
"Register the given `id`, typically a kewyword, with the combination of
|
||||
"Register the given `id`, typically a keyword, with the combination of
|
||||
`db-handler` and an interceptor chain.
|
||||
`db-handler` is a function: (db event) -> db
|
||||
`interceptors` is a collection of interceptors, possibly nested (needs flattenting).
|
||||
`db-handler` is wrapped in an interceptor and added to the end of the chain, so in the end
|
||||
there is only a chain."
|
||||
there is only a chain.
|
||||
The necessary effects and coeffects handler are added to the front of the
|
||||
interceptor chain. These interceptors ensure that app-db is available and updated."
|
||||
([id db-handler]
|
||||
(reg-event-db id nil db-handler))
|
||||
([id interceptors db-handler]
|
||||
(events/register id [base interceptors (db-handler->interceptor db-handler)])))
|
||||
(events/register id [cofx/add-db fx/do-effects interceptors (db-handler->interceptor db-handler)])))
|
||||
|
||||
|
||||
(defn reg-event-fx
|
||||
([id fx-handler]
|
||||
(reg-event-fx id nil fx-handler))
|
||||
([id interceptors fx-handler]
|
||||
(events/register id [base interceptors (fx-handler->interceptor fx-handler)])))
|
||||
(events/register id [cofx/add-db fx/do-effects interceptors (fx-handler->interceptor fx-handler)])))
|
||||
|
||||
|
||||
(defn reg-event-ctx
|
||||
([id handler]
|
||||
(reg-event-ctx id nil handler))
|
||||
([id interceptors handler]
|
||||
(events/register id [base interceptors (ctx-handler->interceptor handler)])))
|
||||
(events/register id [cofx/add-db fx/do-effects interceptors (ctx-handler->interceptor handler)])))
|
||||
|
||||
|
||||
;; -- Logging -----
|
||||
|
|
|
@ -2,32 +2,45 @@
|
|||
(:require
|
||||
[re-frame.router :as router]
|
||||
[re-frame.db :refer [app-db]]
|
||||
[re-frame.interceptor :refer [->interceptor]]
|
||||
[re-frame.interop :refer [set-timeout!]]
|
||||
[re-frame.events :as events]
|
||||
[re-frame.registrar :refer [get-handler clear-handlers register-handler]]
|
||||
[re-frame.interop :refer [ratom? set-timeout!]]
|
||||
[re-frame.loggers :refer [console]]))
|
||||
|
||||
;; ---- Spec schema -----------------------------------------------------------
|
||||
;; TODO use Spec to validate events for :dispatch and :dispatch-n when clj 1.9
|
||||
;; e.g. (when (= :cljs.spec/invalid (s/conform ::event val))
|
||||
;; (console :error (s/explain-str ::event val)))
|
||||
;; (s/def ::event (s/and vector? (complement empty?)))
|
||||
;; (s/def ::events-n (s/coll-off ::event))
|
||||
|
||||
;; -- Registration ------------------------------------------------------------
|
||||
|
||||
|
||||
(def kind :fx)
|
||||
(assert (re-frame.registrar/kinds kind))
|
||||
(def register (partial register-handler kind))
|
||||
|
||||
(defn register
|
||||
[id handler-fn]
|
||||
(register-handler kind id handler-fn))
|
||||
;; -- Interceptor -------------------------------------------------------------
|
||||
|
||||
(def do-effects
|
||||
"An interceptor which performs a `context's` (side) `:effects`.
|
||||
|
||||
For every key in the `:effects` map, call the handler previously
|
||||
registered via `reg-fx`.
|
||||
|
||||
So, if `:effects` was:
|
||||
{:dispatch [:hello 42]
|
||||
:db {...}
|
||||
:undo \"set flag\"}
|
||||
call the registered effects handlers for each of the map's keys:
|
||||
`:dispatch`, `:undo` and `:db`."
|
||||
(->interceptor
|
||||
:name :do-effects
|
||||
:after (fn do-effects-after
|
||||
[context]
|
||||
(->> (:effects context)
|
||||
(map (fn [[key val]]
|
||||
(if-let [effect-fn (get-handler kind key)]
|
||||
(effect-fn val))))
|
||||
doall))))
|
||||
|
||||
;; -- Standard Builtin Effects Handlers --------------------------------------
|
||||
|
||||
|
||||
;; :dispatch-later
|
||||
;;
|
||||
;; `dispatch` one or more events in given times on the future. Expects a collection
|
||||
|
|
|
@ -2,16 +2,13 @@
|
|||
(:require
|
||||
[re-frame.interop :refer [ratom?]]
|
||||
[re-frame.loggers :refer [console]]
|
||||
[re-frame.interop :refer [empty-queue debug-enabled?]]
|
||||
[re-frame.db :refer [app-db]]
|
||||
[re-frame.registrar :as registrar]
|
||||
[clojure.data :as data]))
|
||||
[re-frame.interop :refer [empty-queue debug-enabled?]]))
|
||||
|
||||
|
||||
;; XXX use defrecord ??
|
||||
|
||||
(def mandatory-interceptor-keys #{:name :after :before})
|
||||
|
||||
;; XXX use defrecord ??
|
||||
|
||||
(defn interceptor?
|
||||
[m]
|
||||
|
@ -26,17 +23,12 @@
|
|||
(if-let [unknown-keys (seq (clojure.set/difference
|
||||
(-> m keys set)
|
||||
mandatory-interceptor-keys))]
|
||||
(console :error "re-frame: unknown interceptor keys: " unknown-keys)))
|
||||
(console :error "re-frame: interceptor " name " has unknown keys: " unknown-keys)))
|
||||
{:name (or name :unnamed)
|
||||
:before before
|
||||
:after after })
|
||||
|
||||
;; -- Helpers ------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
(defn get-coeffect
|
||||
[context key]
|
||||
(get-in context [:coeffects key]))
|
||||
;; -- Effect Helpers -----------------------------------------------------------------------------
|
||||
|
||||
(defn get-effect
|
||||
([context key]
|
||||
|
@ -44,33 +36,29 @@
|
|||
([context]
|
||||
(:effects context)))
|
||||
|
||||
|
||||
(defn assoc-effect
|
||||
[context key value]
|
||||
(assoc-in context [:effects key] value))
|
||||
|
||||
;; -- CoEffect Helpers ---------------------------------------------------------------------------
|
||||
|
||||
(defn get-coeffect
|
||||
[context key]
|
||||
(get-in context [:coeffects key]))
|
||||
|
||||
(defn assoc-coeffect
|
||||
[context key value]
|
||||
(assoc-in context [:coeffects key] value))
|
||||
|
||||
|
||||
;; -- Execute Interceptor Chain ------------------------------------------------------------------
|
||||
|
||||
|
||||
(defn- invoke-fn
|
||||
(defn- invoke-interceptor-fn
|
||||
[context interceptor direction]
|
||||
(if-let [f (get interceptor direction)]
|
||||
(f context)
|
||||
context))
|
||||
|
||||
;; XXX on figwheel reload, should invalidate all re-frame subscriptions
|
||||
|
||||
(defn clean-context
|
||||
[context]
|
||||
(if (map? context)
|
||||
(dissoc context :stack :queue)
|
||||
context))
|
||||
|
||||
(defn- invoke-interceptors
|
||||
"Loop over all interceptors, calling `direction` function on each,
|
||||
|
@ -80,31 +68,41 @@
|
|||
|
||||
Each iteration, the next interceptor to process is obtained from
|
||||
context's `:queue`. After they are processed, interceptors are popped
|
||||
from `:queue` and added to into `:stack`.
|
||||
from `:queue` and added to `:stack`.
|
||||
|
||||
After sufficient iteration, `:queue` will be empty, and `:stack` will
|
||||
contain all interceptors processed.
|
||||
|
||||
Returns updated context."
|
||||
Returns updated `context`. Ie. the `context` which has been threaded
|
||||
through all interceptor functions.
|
||||
|
||||
Generally speaking, an interceptor's `:before` fucntion will (if present)
|
||||
add to a `context's` `:coeffect`, while it's `:after` function
|
||||
will modify the `context`'s `:effect`. Very approximately.
|
||||
|
||||
But because all interceptor functions are given `context`, and can
|
||||
return a modified version of it, the way is clear for an interceptor
|
||||
to introspect the stack or queue, or even modify the queue
|
||||
(add new interceptors via `enqueue`?). This is a very fluid arrangement."
|
||||
([context direction]
|
||||
(loop [context context]
|
||||
(let [queue (:queue context)] ;; future interceptors
|
||||
(let [queue (:queue context)] ;; future interceptors
|
||||
(if (empty? queue)
|
||||
context
|
||||
(let [interceptor (peek queue)
|
||||
(let [interceptor (peek queue) ;; next interceptor to call
|
||||
stack (:stack context)] ;; already completed interceptors
|
||||
(recur (-> context
|
||||
(assoc :queue (pop queue))
|
||||
(assoc :stack (conj stack interceptor))
|
||||
(invoke-fn interceptor direction)))))))))
|
||||
(invoke-interceptor-fn interceptor direction)))))))))
|
||||
|
||||
|
||||
(defn enqueue
|
||||
"Adds a collection of interceptors to the end of context's execution queue.
|
||||
Returns updated context.
|
||||
"Add a collection of `interceptors` to the end of `context's` execution `:queue`.
|
||||
Returns the updated `context`.
|
||||
|
||||
In advanced cases, where an interceptor itself wanted to add to the queue,
|
||||
it would call this function (on the context provided to it)"
|
||||
In an advanced case, this function would allow an interceptor could add new
|
||||
interceptors to the `:queue` of a context."
|
||||
[context interceptors]
|
||||
(update context :queue
|
||||
(fnil into empty-queue)
|
||||
|
@ -112,386 +110,81 @@
|
|||
|
||||
|
||||
(defn- context
|
||||
"Return a fresh context"
|
||||
"Create a fresh context"
|
||||
([event interceptors]
|
||||
(-> {}
|
||||
(assoc-coeffect :event event)
|
||||
(enqueue interceptors)))
|
||||
|
||||
([event interceptors db]
|
||||
([event interceptors db] ;; only used in tests, probably a hack, remove ? XXX
|
||||
(-> (context event interceptors)
|
||||
(assoc-coeffect :db db))))
|
||||
|
||||
|
||||
(defn- change-direction
|
||||
"Called on completion of `:before` processing, this function prepares/modifies
|
||||
`context` for the backwards sweep of processing in which `:after` fns are
|
||||
called.
|
||||
`context` for the backwards sweep of processing in which an interceptor
|
||||
chain's `:after` fns are called.
|
||||
|
||||
At this point in processing, the `:queue` is empty and `:stack` holds all
|
||||
interceptors. To enable a backwards walk, the job is to prime the `:queue`
|
||||
with the reverse of what's in `:stack`"
|
||||
the previously run interceptors. So this function enables the backwards walk
|
||||
by priming `:queue` with what's currently in `:stack`"
|
||||
[context]
|
||||
(-> context
|
||||
(dissoc :queue)
|
||||
(enqueue (-> context :stack ))))
|
||||
(enqueue (:stack context))))
|
||||
|
||||
|
||||
(defn execute
|
||||
"Executes a queue of interceptors for a given event.
|
||||
"Executes the given chain (coll) of interceptors.
|
||||
|
||||
An interceptor has this form:
|
||||
{:before (fn [context] ...) ;; identity would be a noop
|
||||
:after (fn [context] ...)}
|
||||
Each interceptor has this form:
|
||||
{:before (fn [context] ...) ;; returns possibly modified context
|
||||
:after (fn [context] ...)} ;; `identity` would be a noop
|
||||
|
||||
Walk the queue of iterceptors from beginning to end calling the `:before` fn on
|
||||
each, then reverse direction, and walk backwards, calling the `:after` fn on each.
|
||||
Walks the queue of iterceptors from beginning to end, calling the
|
||||
`:before` fn on each, then reverse direction and walk backwards,
|
||||
calling the `:after` fn on each.
|
||||
|
||||
The last interceptor in the chain presumably wraps an event handler fn.
|
||||
The last interceptor in the chain presumably wraps an event
|
||||
handler fn. So the overall goal of the process is to \"handle
|
||||
the given event\".
|
||||
|
||||
Thread a `context` through all calls. `context` has this form:
|
||||
|
||||
{:coeffects {:event event
|
||||
{:coeffects {:event [:a-query-id :some-param]
|
||||
:db <original contents of app-db>}
|
||||
:effects {:db <new value for app-db>
|
||||
:dispatch [:something]} ;; example of other effects
|
||||
:dispatch [:an-event-id :param1]}
|
||||
:queue <a collection of further interceptors>
|
||||
:stack <a collection of interceptors already walked>}
|
||||
|
||||
`context` has `:coeffects` and `:effects` which, if this was a web server, would
|
||||
be somewhat anologous to `request` and `response`.
|
||||
`context` has `:coeffects` and `:effects` which, if this was a web
|
||||
server, would be somewhat anologous to `request` and `response`
|
||||
respectively.
|
||||
|
||||
`coeffects` contains information like `event` and the initial state of `db` - ie. the
|
||||
inputs required by the event handler (sitting presumably on the end of the chain),
|
||||
while handler-required side effects are assoc-ed in `:effects` including, but not limited
|
||||
to, new values for `db`.
|
||||
`coeffects` will contain information like `event` and the initial
|
||||
state of `db` - ie. the inputs required by the event handler
|
||||
(sitting presumably on the end of the chain), while handler-returned
|
||||
side effects are assoc-ed in `:effects` including, but not limited to,
|
||||
new values for `db`.
|
||||
|
||||
The first interceptor in a chain will likely have a :before function
|
||||
which adds the current state of app-db into `:coeffects`. OR, it might instead
|
||||
add the connection for a DataScript database, or any other inputs required.
|
||||
And subsequent interceptors may further add to coeffects via their :before too.
|
||||
The first few interceptor in a chain will likely have `:before`
|
||||
functions which \"prime\" the `context` by adding the event, and
|
||||
the current state of app-db into `:coeffects`. OR, it might instead
|
||||
add the connection for a DataScript database, or any other inputs
|
||||
required.
|
||||
|
||||
Equally, this same first interceptor will likely have an `:after` fn which can process
|
||||
all the side effects accumulated into `:effects` including but, not limited to,
|
||||
updates to app-db.
|
||||
Equally, some interceptors in the chain will have `:after` fn
|
||||
which can process the side effects accumulated into `:effects`
|
||||
including but, not limited to, updates to app-db.
|
||||
|
||||
Through both stages (before and after), `context` contains a `:queue` of interceptors yet to be
|
||||
processed, and a `:stack` of interceptors already done. In advanced cases,
|
||||
these values can be modified by the functions through which the context is threaded."
|
||||
Through both stages (before and after), `context` contains a `:queue`
|
||||
of interceptors yet to be processed, and a `:stack` of interceptors
|
||||
already done. In advanced cases, these values can be modified by the
|
||||
functions through which the context is threaded."
|
||||
[event-v interceptors]
|
||||
(-> (context event-v interceptors)
|
||||
(invoke-interceptors :before)
|
||||
change-direction
|
||||
(invoke-interceptors :after)))
|
||||
|
||||
|
||||
;; -- Standard Interceptors -----------------------------------------------------------------------
|
||||
;; these could be in their own library
|
||||
|
||||
(def base
|
||||
"An interceptor which injects/extracts the value of app-db intto/from a context.
|
||||
Used for XXXX "
|
||||
(->interceptor
|
||||
:name :base
|
||||
:before (fn base-before
|
||||
[context]
|
||||
(assoc-coeffect context :db @app-db)) ;; a coeffect for the handler
|
||||
:after (fn base-after
|
||||
[context]
|
||||
(->> (:effects context)
|
||||
(map (fn [[key val]]
|
||||
(if-let [effect-fn (registrar/get-handler :fx key)] ;; XXX shouldn't be using raw :fx
|
||||
(effect-fn val))))
|
||||
doall))))
|
||||
|
||||
|
||||
;; XXX how to stub this out for testing purposes??
|
||||
|
||||
#_(def now
|
||||
"An example interceptor (of dubious utility) which is an example of adding
|
||||
to a handler's coeffects. This interceptor adds the current datetime to coeffects under
|
||||
the `:now` key.
|
||||
|
||||
Why? We want out handlers to be as pure as possible. If a handler calls `js/Date.` then
|
||||
it stops being as pure. What if it needs a random number? These kinds of needed
|
||||
\"inputs\" are referred to `coeffects` (sometimes called side-causes).
|
||||
|
||||
usage:
|
||||
(reg-event-fx ;; notice use of `-fx` registration
|
||||
:some-id
|
||||
[i1 i2 now] ;; notice use of `now` as one of the handler's interceptors
|
||||
(fn [world event] ;; world is the handler's coeffect
|
||||
(let [dt (:now world)] ;; `:now` is available becaue `now` put it there.
|
||||
...)))
|
||||
|
||||
As an exercise, consider how you would write a `random` interceptor which adds a random
|
||||
number into a handler's coeffect?
|
||||
"
|
||||
(->interceptor
|
||||
:name :now
|
||||
:before (fn now-before
|
||||
[context]
|
||||
(assoc-coeffect context :now (js/Date.)))))
|
||||
|
||||
|
||||
(def debug
|
||||
"An interceptor which logs data about the handling of an event.
|
||||
|
||||
Includes a `clojure.data/diff` of the db, before vs after, showing the changes
|
||||
caused by the event handler.
|
||||
|
||||
You'd typically want this interceptor after (to the right of) any path interceptor.
|
||||
|
||||
Warning: calling clojure.data/diff on large, complex data structures can be slow.
|
||||
So, you won't want this interceptor present in production code. See the todomvc
|
||||
example to see how to exclude interceptors from production code."
|
||||
(->interceptor
|
||||
:name :debug
|
||||
:before (fn debug-before
|
||||
[context]
|
||||
#_(console :log "Handling re-frame event: " (-> context :coeffects :event))
|
||||
context)
|
||||
:after (fn debug-after
|
||||
[context]
|
||||
(let [event (get-coeffect context :event)
|
||||
orig-db (get-coeffect context :db)
|
||||
new-db (get-effect context :db)]
|
||||
(if-not new-db
|
||||
(do
|
||||
(console :log "no app-db changes caused by: " event)
|
||||
context)
|
||||
(let [[only-before only-after] (data/diff orig-db new-db) ;; diff between effe
|
||||
db-changed? (or (some? only-before) (some? only-after))]
|
||||
(if db-changed?
|
||||
(do (console :group "db clojure.data/diff for: " event)
|
||||
(console :log "only before: " only-before)
|
||||
(console :log "only after : " only-after)
|
||||
(console :groupEnd))
|
||||
(console :log "no app-db changes caused by: " event))
|
||||
context))))))
|
||||
|
||||
|
||||
(def trim-v
|
||||
"An interceptor which removes the first element of the event vector, allowing you to write
|
||||
more aesthetically pleasing db handlers. No leading underscore on the event-v!
|
||||
Your event handlers will look like this:
|
||||
|
||||
(defn my-handler
|
||||
[db [x y z]] ;; <-- instead of [_ x y z]
|
||||
....)"
|
||||
(->interceptor
|
||||
:name :trim-v
|
||||
:before (fn trimv-before
|
||||
[context]
|
||||
(->> (get-coeffect context :event)
|
||||
rest
|
||||
vec
|
||||
(assoc-coeffect context :event)))))
|
||||
|
||||
|
||||
;; -- Interceptor Factories - PART 1 ---------------------------------------------------------------
|
||||
;;
|
||||
;; These factories wrap the 3 kinds of handlers.
|
||||
;;
|
||||
|
||||
(defn db-handler->interceptor
|
||||
"Returns an interceptor which wraps the kind of event handler given to `reg-event-db`.
|
||||
|
||||
These handlers take two arguments; `db` and `event`, and they return `db`.
|
||||
|
||||
(fn [db event]
|
||||
....)
|
||||
|
||||
So, the interceptor wraps the given handler:
|
||||
1. extracts two coeffects (from context): db and event
|
||||
2. calls handler-fn
|
||||
3. stores the db result back into context's effects"
|
||||
[handler-fn]
|
||||
(->interceptor
|
||||
:name :db-handler
|
||||
:before (fn db-handler-before
|
||||
[context]
|
||||
(let [{:keys [db event]} (:coeffects context)]
|
||||
(->> (handler-fn db event)
|
||||
(assoc-effect context :db))))))
|
||||
|
||||
|
||||
(defn fx-handler->interceptor
|
||||
"Returns an interceptor which wraps the kind of event handler given to `reg-event-fx`.
|
||||
|
||||
These handlers take two arguments; `world` and `event`, and they return `effects`.
|
||||
|
||||
(fn [world event]
|
||||
{:db ...
|
||||
:dispatch ...})
|
||||
|
||||
Wrap handler in an interceptor so it can be added to (the RHS) of a chain:
|
||||
1. extracts necessary coeffects
|
||||
2. call handler-fn
|
||||
3. stores the result backinto the effects"
|
||||
[handler-fn]
|
||||
(->interceptor
|
||||
:name :fx-handler
|
||||
:before (fn fx-handler-before
|
||||
[context]
|
||||
(let [{:keys [event] :as coeffects} (:coeffects context)]
|
||||
(->> (handler-fn coeffects event)
|
||||
(assoc context :effects))))))
|
||||
|
||||
|
||||
(defn ctx-handler->interceptor
|
||||
"Returns an interceptor which wraps the kind of event handler given to `reg-event-ctx`.
|
||||
These advanced handlers take one argument: `context` and they return a modified `context`.
|
||||
Example:
|
||||
(fn [context]
|
||||
(enqueue context [more interceptors]))"
|
||||
[handler-fn]
|
||||
(->interceptor
|
||||
:name :ctx-handler
|
||||
:before handler-fn))
|
||||
|
||||
|
||||
;; -- Interceptors Factories - PART 2 ------------------------------------------------------------
|
||||
;;
|
||||
;; I.e. functions which return interceptors
|
||||
;;
|
||||
|
||||
|
||||
(defn path
|
||||
"An interceptor factory which supplies a sub-path of `:db` to the handler.
|
||||
Is somewhat annologous to `update-in`. It grafts the return value from the handler
|
||||
back into db.
|
||||
|
||||
Usage:
|
||||
(path :some :path)
|
||||
(path [:some :path])
|
||||
(path [:some :path] :to :here)
|
||||
(path [:some :path] [:to] :here)
|
||||
|
||||
Implementation:
|
||||
- in :before, store the original db in within `context`
|
||||
- ib :after, re-establish original db with modification
|
||||
- remember: path may be used twice within the one interceptor chain.
|
||||
"
|
||||
[& args]
|
||||
(let [path (flatten args)
|
||||
db-store-key :re-frame-path/original-dbs] ;; this is where, within `context`, we store the original db
|
||||
(when (empty? path)
|
||||
(console :error "re-frame: \"path\" interceptor given no params."))
|
||||
(->interceptor
|
||||
:name :path
|
||||
:before (fn
|
||||
[context]
|
||||
(let [original-db (get-coeffect context :db)]
|
||||
(-> context
|
||||
(update db-store-key conj original-db)
|
||||
(assoc-coeffect :db (get-in original-db path)))))
|
||||
:after (fn [context]
|
||||
(let [db-store (db-store-key context)
|
||||
original-db (peek db-store)
|
||||
new-store (pop db-store)
|
||||
full-db (->> (get-effect context :db)
|
||||
(assoc-in original-db path))]
|
||||
(-> context
|
||||
(assoc db-store-key new-store)
|
||||
(assoc-effect :db full-db)))))))
|
||||
|
||||
|
||||
|
||||
;; XXX in todomvc what about a coeffect which is the value in LocalStore ??
|
||||
|
||||
(defn enrich
|
||||
"Interceptor factory which runs a given function \"f\" in the \"after handler\"
|
||||
position. \"f\" is (db v) -> db
|
||||
|
||||
Unlike the \"after\" inteceptor which is only about side effects, \"enrich\"
|
||||
expects f to process and alter the :db coeffect in some useful way, contributing
|
||||
to the derived data, flowing vibe.
|
||||
|
||||
Imagine that todomvc needed to do duplicate detection - if any two todos had
|
||||
the same text, then highlight their background, and report them in a warning
|
||||
down the bottom.
|
||||
|
||||
Almost any action (edit text, add new todo, remove a todo) requires a
|
||||
complete reassesment of duplication errors and warnings. Eg: that edit
|
||||
update might have introduced a new duplicate or removed one. Same with a
|
||||
todo removal.
|
||||
|
||||
And to perform this enrichment, a function has to inspect all the todos,
|
||||
possibly set flags on each, and set some overall list of duplicates.
|
||||
And this duplication check might just be one check among many.
|
||||
|
||||
\"f\" would need to be both adding and removing the duplicate warnings.
|
||||
By applying \"f\" in middleware, we keep the handlers simple and yet we
|
||||
ensure this important step is not missed."
|
||||
[f]
|
||||
(->interceptor
|
||||
:name :enrich
|
||||
:after (fn enrich-after
|
||||
[context]
|
||||
(let [event (get-coeffect context :event)
|
||||
db (get-effect context :db)]
|
||||
(->> (f db event)
|
||||
(assoc-effect context :db))))))
|
||||
|
||||
|
||||
|
||||
(defn after
|
||||
"Interceptor factory which runs a given function `f` in the \"after\"
|
||||
position presumably for side effects.
|
||||
|
||||
`f` is called with two arguments: the `effects` value of `:db` and the event. It's return
|
||||
value is ignored so `f` can only side-effect. Example uses:
|
||||
- `f` runs schema validation (reporting any errors found)
|
||||
- `f` writes some aspect of db to localstorage."
|
||||
[f]
|
||||
(->interceptor
|
||||
:name :after
|
||||
:after (fn after-after
|
||||
[context]
|
||||
(let [db (get-effect context :db)
|
||||
event (get-coeffect context :event)]
|
||||
(f db event) ;; call f for side effects
|
||||
context)))) ;; context is unchanged
|
||||
|
||||
|
||||
(defn on-changes
|
||||
"Interceptor factory which acts a bit like `reaction` (but it flows into `db`, rather than out)
|
||||
It observes N paths in `db` and if any of them test not indentical? to their previous value
|
||||
(as a result of a handler being run) then it runs 'f' to compute a new value, which is
|
||||
then assoced into the given `out-path` within `db`.
|
||||
|
||||
Usage:
|
||||
|
||||
(defn my-f
|
||||
[a-val b-val]
|
||||
... some computation on a and b in here)
|
||||
|
||||
(on-changes my-f [:c] [:a] [:b])
|
||||
|
||||
Put this Interceptor on the right handlers (ones which might change :a or :b).
|
||||
It will:
|
||||
- call 'f' each time the value at path [:a] or [:b] changes
|
||||
- call 'f' with the values extracted from [:a] [:b]
|
||||
- assoc the return value from 'f' into the path [:c]
|
||||
"
|
||||
[f out-path & in-paths]
|
||||
(->interceptor
|
||||
:name :enrich
|
||||
:after (fn on-change-after
|
||||
[context]
|
||||
(let [new-db (get-effect context :db)
|
||||
old-db (get-coeffect context :db)
|
||||
|
||||
;; work out if any "inputs" have changed
|
||||
new-ins (map #(get-in new-db %) in-paths)
|
||||
old-ins (map #(get-in old-db %) in-paths)
|
||||
changed-ins? (some false? (map identical? new-ins old-ins))]
|
||||
|
||||
;; if one of the inputs has changed, then run 'f'
|
||||
(if changed-ins?
|
||||
(->> (apply f new-ins)
|
||||
(assoc-in new-db out-path)
|
||||
(assoc-effect context :db))
|
||||
context)))))
|
||||
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
(ns re-frame.registrar
|
||||
"In many places, re-frame asks you to associate an `id` (keyword)
|
||||
with a `handler` (fucntion). This namespace contains the
|
||||
central registry of such associations."
|
||||
(:require [re-frame.interop :refer [debug-enabled?]]
|
||||
[re-frame.loggers :refer [console]]))
|
||||
|
||||
|
||||
;; kinds of handlers
|
||||
(def kinds #{:event :fx :sub})
|
||||
(def kinds #{:event :fx :cofx :sub})
|
||||
|
||||
;; This atom contains a register of all handlers.
|
||||
;; Is a map keyed first by kind (of handler), and then id.
|
||||
|
|
|
@ -199,14 +199,13 @@
|
|||
|
||||
(-call-post-event-callbacks
|
||||
[_ event-v]
|
||||
;; Call each registed post-event callback.
|
||||
(doseq [callback (vals post-event-callback-fns)]
|
||||
(callback event-v queue)))
|
||||
|
||||
(-resume
|
||||
[this]
|
||||
(-process-1st-event-in-queue this) ;; do the event which paused processing
|
||||
(-run-queue this))) ;; do the rest of the queued events
|
||||
(-run-queue this))) ;; do the rest of the queued events
|
||||
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
|
|
@ -0,0 +1,271 @@
|
|||
(ns re-frame.std-interceptors
|
||||
"contains re-frame supplied, standard interceptors"
|
||||
(:require
|
||||
[re-frame.interceptor :refer [->interceptor get-effect get-coeffect assoc-coeffect assoc-effect]]
|
||||
[re-frame.loggers :refer [console]]
|
||||
[re-frame.registrar :as registrar]
|
||||
[re-frame.db :refer [app-db]]
|
||||
[clojure.data :as data]))
|
||||
|
||||
|
||||
;; XXX provide a way to set what handler should be called when there is no registered handler.
|
||||
;; by default this handler will simply print out a message saying no handler was found.
|
||||
|
||||
|
||||
(def debug
|
||||
"An interceptor which logs data about the handling of an event.
|
||||
|
||||
Includes a `clojure.data/diff` of the db, before vs after, showing
|
||||
the changes caused by the event handler.
|
||||
|
||||
You'd typically want this interceptor after (to the right of) any
|
||||
path interceptor.
|
||||
|
||||
Warning: calling clojure.data/diff on large, complex data structures
|
||||
can be slow. So, you won't want this interceptor present in production
|
||||
code. See the todomvc example to see how to exclude interceptors from
|
||||
production code."
|
||||
(->interceptor
|
||||
:name :debug
|
||||
:before (fn debug-before
|
||||
[context]
|
||||
#_(console :log "Handling re-frame event: " (-> context :coeffects :event))
|
||||
context)
|
||||
:after (fn debug-after
|
||||
[context]
|
||||
(let [event (get-coeffect context :event)
|
||||
orig-db (get-coeffect context :db)
|
||||
new-db (get-effect context :db)]
|
||||
(if-not new-db
|
||||
(do
|
||||
(console :log "no app-db changes caused by: " event)
|
||||
context)
|
||||
(let [[only-before only-after] (data/diff orig-db new-db) ;; diff between effe
|
||||
db-changed? (or (some? only-before) (some? only-after))]
|
||||
(if db-changed?
|
||||
(do (console :group "db clojure.data/diff for: " event)
|
||||
(console :log "only before: " only-before)
|
||||
(console :log "only after : " only-after)
|
||||
(console :groupEnd))
|
||||
(console :log "no app-db changes caused by: " event))
|
||||
context))))))
|
||||
|
||||
|
||||
(def trim-v
|
||||
"An interceptor which removes the first element of the event vector, allowing you to write
|
||||
more aesthetically pleasing db handlers. No leading underscore on the event-v!
|
||||
Your event handlers will look like this:
|
||||
|
||||
(defn my-handler
|
||||
[db [x y z]] ;; <-- instead of [_ x y z]
|
||||
....)"
|
||||
(->interceptor
|
||||
:name :trim-v
|
||||
:before (fn trimv-before
|
||||
[context]
|
||||
(->> (get-coeffect context :event)
|
||||
rest
|
||||
vec
|
||||
(assoc-coeffect context :event)))))
|
||||
|
||||
|
||||
;; -- Interceptor Factories - PART 1 ---------------------------------------------------------------
|
||||
;;
|
||||
;; These 3 factories wrap the 3 kinds of handlers.
|
||||
;;
|
||||
|
||||
(defn db-handler->interceptor
|
||||
"Returns an interceptor which wraps the kind of event handler given to `reg-event-db`.
|
||||
|
||||
These handlers take two arguments; `db` and `event`, and they return `db`.
|
||||
|
||||
(fn [db event]
|
||||
....)
|
||||
|
||||
So, the interceptor wraps the given handler:
|
||||
1. extracts two `:coeffects` keys: db and event
|
||||
2. calls handler-fn
|
||||
3. stores the db result back into context's `:effects`"
|
||||
[handler-fn]
|
||||
(->interceptor
|
||||
:name :db-handler
|
||||
:before (fn db-handler-before
|
||||
[context]
|
||||
(let [{:keys [db event]} (:coeffects context)]
|
||||
(->> (handler-fn db event)
|
||||
(assoc-effect context :db))))))
|
||||
|
||||
|
||||
(defn fx-handler->interceptor
|
||||
"Returns an interceptor which wraps the kind of event handler given to `reg-event-fx`.
|
||||
|
||||
These handlers take two arguments; `world` and `event`, and they return `effects`.
|
||||
|
||||
(fn [world event]
|
||||
{:db ...
|
||||
:dispatch ...})
|
||||
|
||||
Wrap handler in an interceptor so it can be added to (the RHS) of a chain:
|
||||
1. extracts `:coeffects`
|
||||
2. call handler-fn giving coeffects
|
||||
3. stores the result back into the `:effects`"
|
||||
[handler-fn]
|
||||
(->interceptor
|
||||
:name :fx-handler
|
||||
:before (fn fx-handler-before
|
||||
[context]
|
||||
(let [{:keys [event] :as coeffects} (:coeffects context)]
|
||||
(->> (handler-fn coeffects event)
|
||||
(assoc context :effects))))))
|
||||
|
||||
|
||||
(defn ctx-handler->interceptor
|
||||
"Returns an interceptor which wraps the kind of event handler given to `reg-event-ctx`.
|
||||
These advanced handlers take one argument: `context` and they return a modified `context`.
|
||||
Example:
|
||||
(fn [context]
|
||||
(enqueue context [more interceptors]))"
|
||||
[handler-fn]
|
||||
(->interceptor
|
||||
:name :ctx-handler
|
||||
:before handler-fn))
|
||||
|
||||
|
||||
;; -- Interceptors Factories - PART 2 ------------------------------------------------------------
|
||||
|
||||
|
||||
(defn path
|
||||
"An interceptor factory which supplies a sub-path of `:db` to the handler.
|
||||
It's action is somewhat annologous to `update-in`. It grafts the return
|
||||
value from the handler back into db.
|
||||
|
||||
Usage:
|
||||
(path :some :path)
|
||||
(path [:some :path])
|
||||
(path [:some :path] :to :here)
|
||||
(path [:some :path] [:to] :here)
|
||||
|
||||
Remember: `path` may be used multiple times within the one interceptor chain.
|
||||
"
|
||||
[& args]
|
||||
(let [path (flatten args)
|
||||
db-store-key :re-frame-path/original-dbs] ;; this is where, within `context`, we store the original db
|
||||
(when (empty? path)
|
||||
(console :error "re-frame: \"path\" interceptor given no params."))
|
||||
(->interceptor
|
||||
:name :path
|
||||
:before (fn
|
||||
[context]
|
||||
(let [original-db (get-coeffect context :db)]
|
||||
(-> context
|
||||
(update db-store-key conj original-db)
|
||||
(assoc-coeffect :db (get-in original-db path)))))
|
||||
:after (fn [context]
|
||||
(let [db-store (db-store-key context)
|
||||
original-db (peek db-store)
|
||||
new-db-store (pop db-store)
|
||||
full-db (->> (get-effect context :db)
|
||||
(assoc-in original-db path))]
|
||||
(-> context
|
||||
(assoc db-store-key new-db-store)
|
||||
(assoc-effect :db full-db)))))))
|
||||
|
||||
|
||||
|
||||
|
||||
(defn enrich
|
||||
"Interceptor factory which runs a given function \"f\" in the \"after handler\"
|
||||
position. \"f\" is (db v) -> db
|
||||
|
||||
Unlike the \"after\" inteceptor which is only about side effects, \"enrich\"
|
||||
expects f to process and alter the :db coeffect in some useful way, contributing
|
||||
to the derived data, flowing vibe.
|
||||
|
||||
Imagine that todomvc needed to do duplicate detection - if any two todos had
|
||||
the same text, then highlight their background, and report them in a warning
|
||||
down the bottom.
|
||||
|
||||
Almost any action (edit text, add new todo, remove a todo) requires a
|
||||
complete reassesment of duplication errors and warnings. Eg: that edit
|
||||
update might have introduced a new duplicate or removed one. Same with a
|
||||
todo removal.
|
||||
|
||||
And to perform this enrichment, a function has to inspect all the todos,
|
||||
possibly set flags on each, and set some overall list of duplicates.
|
||||
And this duplication check might just be one check among many.
|
||||
|
||||
\"f\" would need to be both adding and removing the duplicate warnings.
|
||||
By applying \"f\" in middleware, we keep the handlers simple and yet we
|
||||
ensure this important step is not missed."
|
||||
[f]
|
||||
(->interceptor
|
||||
:name :enrich
|
||||
:after (fn enrich-after
|
||||
[context]
|
||||
(let [event (get-coeffect context :event)
|
||||
db (get-effect context :db)]
|
||||
(->> (f db event)
|
||||
(assoc-effect context :db))))))
|
||||
|
||||
|
||||
|
||||
(defn after
|
||||
"Interceptor factory which runs a given function `f` in the \"after\"
|
||||
position presumably for side effects.
|
||||
|
||||
`f` is called with two arguments: the `effects` value of `:db` and the event. It's return
|
||||
value is ignored so `f` can only side-effect. Example uses:
|
||||
- `f` runs schema validation (reporting any errors found)
|
||||
- `f` writes some aspect of db to localstorage."
|
||||
[f]
|
||||
(->interceptor
|
||||
:name :after
|
||||
:after (fn after-after
|
||||
[context]
|
||||
(let [db (get-effect context :db)
|
||||
event (get-coeffect context :event)]
|
||||
(f db event) ;; call f for side effects
|
||||
context)))) ;; context is unchanged
|
||||
|
||||
|
||||
(defn on-changes
|
||||
"Interceptor factory which acts a bit like `reaction` (but it flows into `db`, rather than out)
|
||||
It observes N paths in `db` and if any of them test not indentical? to their previous value
|
||||
(as a result of a handler being run) then it runs 'f' to compute a new value, which is
|
||||
then assoced into the given `out-path` within `db`.
|
||||
|
||||
Usage:
|
||||
|
||||
(defn my-f
|
||||
[a-val b-val]
|
||||
... some computation on a and b in here)
|
||||
|
||||
(on-changes my-f [:c] [:a] [:b])
|
||||
|
||||
Put this Interceptor on the right handlers (ones which might change :a or :b).
|
||||
It will:
|
||||
- call 'f' each time the value at path [:a] or [:b] changes
|
||||
- call 'f' with the values extracted from [:a] [:b]
|
||||
- assoc the return value from 'f' into the path [:c]
|
||||
"
|
||||
[f out-path & in-paths]
|
||||
(->interceptor
|
||||
:name :enrich
|
||||
:after (fn on-change-after
|
||||
[context]
|
||||
(let [new-db (get-effect context :db)
|
||||
old-db (get-coeffect context :db)
|
||||
|
||||
;; work out if any "inputs" have changed
|
||||
new-ins (map #(get-in new-db %) in-paths)
|
||||
old-ins (map #(get-in old-db %) in-paths)
|
||||
changed-ins? (some false? (map identical? new-ins old-ins))]
|
||||
|
||||
;; if one of the inputs has changed, then run 'f'
|
||||
(if changed-ins?
|
||||
(->> (apply f new-ins)
|
||||
(assoc-in new-db out-path)
|
||||
(assoc-effect context :db))
|
||||
context)))))
|
||||
|
||||
|
|
@ -36,7 +36,7 @@
|
|||
(let [cache-key [query-v dynv]]
|
||||
;; when this reaction is nolonger being used, remove it from the cache
|
||||
(add-on-dispose! r #(do (swap! query->reaction dissoc cache-key)
|
||||
(console :log "Removing subscription: " cache-key))) ;; XXX remove console debug
|
||||
#_(console :log "Removing subscription: " cache-key))) ;; XXX remove console debug
|
||||
;; cache this reaction, so it can be used to deduplicate other, later "=" subscriptions
|
||||
(swap! query->reaction assoc cache-key r)
|
||||
r)) ;; return the actual reaction
|
||||
|
@ -54,18 +54,18 @@
|
|||
"Returns a Reagent/reaction which contains a computation"
|
||||
([query-v]
|
||||
(if-let [cached (cache-lookup query-v)]
|
||||
(do (console :log "Using cached subscription: " query-v)
|
||||
(do ;(console :log "Using cached subscription: " query-v)
|
||||
cached)
|
||||
(let [query-id (first-in-vector query-v)
|
||||
handler-fn (get-handler kind query-id)]
|
||||
(console :log "Subscription created: " query-v)
|
||||
;(console :log "Subscription created: " query-v)
|
||||
(if-not handler-fn
|
||||
(console :error "re-frame: no subscription handler registered for: \"" query-id "\". Returning a nil subscription."))
|
||||
(cache-and-return query-v [] (handler-fn app-db query-v)))))
|
||||
|
||||
([v dynv]
|
||||
(if-let [cached (cache-lookup v dynv)]
|
||||
(do (console :log "Using cached subscription: " v " and " dynv)
|
||||
(do ;(console :log "Using cached subscription: " v " and " dynv)
|
||||
cached)
|
||||
(let [query-id (first-in-vector v)
|
||||
handler-fn (get-handler kind query-id)]
|
||||
|
@ -78,7 +78,7 @@
|
|||
sub (make-reaction (fn [] (handler-fn app-db v @dyn-vals)))]
|
||||
;; handler-fn returns a reaction which is then wrapped in the sub reaction
|
||||
;; need to double deref it to get to the actual value.
|
||||
(console :log "Subscription created: " v dynv)
|
||||
;(console :log "Subscription created: " v dynv)
|
||||
(cache-and-return v dynv (make-reaction (fn [] @@sub)))))))))
|
||||
|
||||
;; -- Helper code for register-pure -------------------
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
(ns re-frame.interceptor-test
|
||||
(:require [cljs.test :refer-macros [is deftest]]
|
||||
[reagent.ratom :refer [atom]]
|
||||
[re-frame.interceptor :refer [context get-coeffect assoc-effect assoc-coeffect get-effect
|
||||
trim-v path on-changes
|
||||
[re-frame.interceptor :refer [context get-coeffect assoc-effect assoc-coeffect get-effect]]
|
||||
[re-frame.std-interceptors :refer [trim-v path on-changes
|
||||
db-handler->interceptor fx-handler->interceptor]]))
|
||||
|
||||
(enable-console-print!)
|
||||
|
|
Loading…
Reference in New Issue