re-frame/src/re_frame/handlers.cljs

126 lines
4.9 KiB
Plaintext
Raw Normal View History

2014-12-15 11:56:32 +00:00
(ns re-frame.handlers
(:refer-clojure :exclude [flush])
(:require-macros [cljs.core.async.macros :refer [go-loop go]])
2015-03-02 14:03:09 +00:00
(:require [reagent.core :refer [flush]]
[re-frame.db :refer [app-db]]
; [re-frame.middleware :refer [noop]]
[re-frame.utils :refer [first-in-vector warn]]
[cljs.core.async :refer [chan put! <! timeout]]))
2014-12-08 03:48:59 +00:00
;; -- composing middleware -----------------------------------------------------------------------
(defn comp-middleware
"Given a vector of middleware, filter out any nils, and use \"comp\" to compose them.
v can have nested vectors, and will be flattended before \"comp\" is applied.
For convienience, if v turns out to already be a function (assumed to be
2015-03-02 14:03:09 +00:00
middleware), just return it.
Filtering out nils allows us to create Middlewhere conditionally like this:
(comp-middleware [pure (when debug? debug)]) ;; that 'when' might leave a nil
"
[v]
(cond
2015-03-02 14:03:09 +00:00
(fn? v) v ;; assumed to be existing middleware
(vector? v) (let [v (remove nil? (flatten v))]
(cond
2015-03-02 14:03:09 +00:00
(empty? v) (fn [h] h) ;; noop middleware
(= 1 (count v)) (first v)
:else (apply comp v)))
:else (assert (vector? v)
(str "re-frame: compose expects a vector, got: " v))))
2015-03-02 14:03:09 +00:00
;; -- the register of event handlers --------------------------------------------------------------
(def ^:private id->fn (atom {}))
2014-12-15 11:56:32 +00:00
2015-03-02 14:03:09 +00:00
(defn lookup-handler
[event-id]
(get @id->fn event-id))
2014-12-15 11:56:32 +00:00
(defn register
"register a handler for an event"
([event-id handler-fn]
(when (contains? @id->fn event-id)
(warn "re-frame: overwriting an event-handler for: " event-id)) ;; allow it, but warn.
(swap! id->fn assoc event-id handler-fn))
2014-12-15 11:56:32 +00:00
([event-id middleware handler-fn]
2015-03-02 14:03:09 +00:00
(let [mid-ware (comp-middleware middleware)
hander-fn (mid-ware handler-fn)]
(register event-id hander-fn))))
;; -- The Event Conveyor Belt --------------------------------------------------------------------
;;
2015-02-22 11:28:40 +00:00
;; Moves events from "dispatch" to the router loop.
2015-03-01 20:57:11 +00:00
;; Allows for the aysnc handling of events.
;;
2015-03-02 12:03:49 +00:00
(def ^:private event-chan (chan)) ;; TODO: set buffer size?
2015-02-22 11:28:40 +00:00
;; -- lookup and call -----------------------------------------------------------------------------
2015-03-02 12:03:49 +00:00
(defn handle
2015-03-01 20:57:11 +00:00
"Given an event vector, look up the right handler, then call it.
By default, handlers are not assumed to be pure. They are called with
two paramters:
- the `app-db` atom and
- the event vector
The handler is assumed to side-effect on the atom, the return value is ignored.
2015-03-02 12:03:49 +00:00
To write pure handlers, use the \"pure\" middleware when registering the handler."
[event-v]
(let [event-id (first-in-vector event-v)
2015-03-02 12:03:49 +00:00
handler-fn (lookup-handler event-id)]
(if (nil? handler-fn)
2015-02-22 11:28:40 +00:00
(warn "re-frame: no event handler registered for: \"" event-id "\". Ignoring.") ;; TODO: make exception
(handler-fn app-db event-v))))
2015-02-22 11:28:40 +00:00
;; -- router loop ---------------------------------------------------------------------------------
;;
2015-03-01 20:57:11 +00:00
;; In a perpretual loop, read events from the dispatch channel, and route them
2015-02-22 11:28:40 +00:00
;; to the right handler.
;;
;; Because handlers occupy the CPU, before each event is handled, hand
2015-02-22 11:28:40 +00:00
;; back control to the browser, via a (<! (timeout 0)) call.
;;
;; In odd cases, we need to pause for an entire annimationFrame, to ensure that
;; the DOM is fully flushed, before then calling a handler known to hog the CPU
;; for an extended period. In such a case, the event should be laballed with metadata
2015-03-01 20:57:11 +00:00
;; Example usage (notice the ":flush-dom" metadata):
;; (dispatch ^:flush-dom [:event-id other params])
;;
;; router loop
(go-loop []
(let [event-v (<! event-chan) ;; wait for an event
_ (if (:flush-dom (meta event-v)) ;; check the event for metadata
(do (flush) (<! (timeout 20))) ;; wait just over one annimation frame (16ms), to rensure all pending GUI work is flushed to the DOM.
(<! (timeout 0)))] ;; just in case we are handling one dispatch after an other, give the browser back control to do its stuff
(handle event-v)
(recur)))
2015-02-22 11:28:40 +00:00
;; -- dispatch ------------------------------------------------------------------------------------
(defn dispatch
2015-03-02 12:03:49 +00:00
"Send an event to be processed by the registered handler.
2014-12-15 11:56:32 +00:00
Usage example:
2015-03-02 12:03:49 +00:00
(dispatch [:delete-item 42])
"
2014-12-15 11:56:32 +00:00
[event-v]
(if (nil? event-v)
2015-02-22 11:28:40 +00:00
(warn "re-frame: \"dispatch\" is ignoring a nil event.") ;; nil would close the channel
2015-03-01 20:57:11 +00:00
(put! event-chan event-v))
nil) ;; Ensure nil return. See https://github.com/Day8/re-frame/wiki/Returning-False
2015-03-01 20:57:11 +00:00
;; TODO: remove sync handling. I don't like it much, even for testing.
2015-01-05 21:49:20 +00:00
(defn dispatch-sync
2015-02-22 11:28:40 +00:00
"Invoke the event handler sycronously, avoiding the async-inducing use of core.async/chan"
2015-01-05 21:49:20 +00:00
[event-v]
(handle event-v))
2014-12-08 03:48:59 +00:00