2014-12-15 11:56:32 +00:00
|
|
|
(ns re-frame.handlers
|
2015-02-20 05:46:40 +00:00
|
|
|
(:refer-clojure :exclude [flush])
|
|
|
|
(:require-macros [cljs.core.async.macros :refer [go-loop go]])
|
|
|
|
(:require [reagent.core :refer [flush]]
|
|
|
|
[re-frame.db :refer [app-db]]
|
2015-02-22 11:28:40 +00:00
|
|
|
[re-frame.utils :refer [first-in-vector warn]]
|
2015-02-18 08:43:50 +00:00
|
|
|
[cljs.core.async :refer [chan put! <! timeout]]))
|
2014-12-08 03:48:59 +00:00
|
|
|
|
|
|
|
|
2015-02-22 11:28:40 +00:00
|
|
|
;; -- register of event handlers ------------------------------------------------------------------------
|
2014-12-20 02:12:40 +00:00
|
|
|
|
2015-02-20 13:47:01 +00:00
|
|
|
(def ^:private id->fn (atom {}))
|
2014-12-15 11:56:32 +00:00
|
|
|
|
|
|
|
(defn register
|
|
|
|
"register a handler for an event"
|
|
|
|
[event-id handler-fn]
|
2015-02-20 13:47:01 +00:00
|
|
|
(when (contains? @id->fn event-id)
|
2015-02-22 11:28:40 +00:00
|
|
|
(warn "re-frame: overwriting an event-handler" event-id)) ;; allow it, but warn.
|
2015-02-20 13:47:01 +00:00
|
|
|
(swap! id->fn assoc event-id handler-fn))
|
2014-12-15 11:56:32 +00:00
|
|
|
|
|
|
|
|
2015-02-20 13:47:01 +00:00
|
|
|
;; -- The Event Conveyor Belt --------------------------------------------------------------------
|
|
|
|
;;
|
2015-02-22 11:28:40 +00:00
|
|
|
;; Moves events from "dispatch" to the router loop.
|
|
|
|
;; Key architecutal purpose is to cause aysnc handling of events.
|
|
|
|
(def ^:private event-chan (chan)) ;; TODO: how big should we make the buffer?
|
2014-12-20 02:12:40 +00:00
|
|
|
|
2015-02-20 13:47:01 +00:00
|
|
|
|
2015-02-22 11:28:40 +00:00
|
|
|
;; -- lookup and call -----------------------------------------------------------------------------
|
2015-02-20 13:47:01 +00:00
|
|
|
|
|
|
|
(defn- handle
|
|
|
|
"Look up the handler for the given event, then call it, passing in 2 parameters."
|
|
|
|
[event-v]
|
|
|
|
(let [event-id (first-in-vector event-v)
|
|
|
|
handler-fn (get @id->fn 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
|
2015-02-20 13:47:01 +00:00
|
|
|
(handler-fn app-db event-v))))
|
|
|
|
|
|
|
|
|
2015-02-22 11:28:40 +00:00
|
|
|
;; -- router loop ---------------------------------------------------------------------------------
|
|
|
|
;;
|
|
|
|
;; In a loop, read events from the dispatch channel, and route them
|
|
|
|
;; to the right handler.
|
2015-02-20 13:47:01 +00:00
|
|
|
;;
|
|
|
|
;; 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.
|
2015-02-20 13:47:01 +00:00
|
|
|
;;
|
|
|
|
;; In odd cases, we need to pause for an entire annimationFrame, to ensure that
|
2015-02-22 11:28:40 +00:00
|
|
|
;; the DOM is fully flushed, before thencalling a handler known to hog the CPU
|
|
|
|
;; for an extended period. In such a case, the event should have metadata
|
|
|
|
;; Example usage:
|
2015-02-20 13:47:01 +00:00
|
|
|
;; (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))
|
|
|
|
(do (flush) (<! (timeout 20))) ;; wait just over one annimation frame (16ms), to rensure all pending GUI work is done.
|
|
|
|
(<! (timeout 0)))] ;; just in case we are handling one dispatch after an other, give the GUI a chance to do its stuff.
|
|
|
|
(handle event-v)
|
|
|
|
(recur)))
|
|
|
|
|
|
|
|
|
2015-02-22 11:28:40 +00:00
|
|
|
;; -- dispatch ------------------------------------------------------------------------------------
|
2014-12-20 02:12:40 +00:00
|
|
|
|
2014-12-11 08:24:19 +00:00
|
|
|
(defn dispatch
|
2015-02-20 13:47:01 +00:00
|
|
|
"reagent components use this function to send events.
|
2014-12-15 11:56:32 +00:00
|
|
|
Usage example:
|
|
|
|
(dispatch [:delete-item 42])"
|
|
|
|
[event-v]
|
2015-02-20 13:47:01 +00:00
|
|
|
(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-02-20 13:47:01 +00:00
|
|
|
(put! event-chan event-v)))
|
2014-12-20 02:12:40 +00:00
|
|
|
|
2015-02-20 13:47:01 +00:00
|
|
|
|
|
|
|
;; TODO: remove sync handling. I don't like it, 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]
|
2015-02-20 13:47:01 +00:00
|
|
|
(handle event-v))
|
2014-12-20 02:12:40 +00:00
|
|
|
|
2014-12-08 03:48:59 +00:00
|
|
|
|
2014-12-20 02:12:40 +00:00
|
|
|
;; -- helper --------------------------------------------------------------------------------------
|
2014-12-08 03:48:59 +00:00
|
|
|
|
2015-02-20 13:47:01 +00:00
|
|
|
;; TODO: Yuck. this has to go.
|
2014-12-15 11:56:32 +00:00
|
|
|
(defn transaction!
|
|
|
|
"A helper fucntion to be used when writting event handlers.
|
|
|
|
Allows a handler to perform an atomic modification of the atom.
|
|
|
|
Modification consist of one or more mutations, wrapped by a function,
|
|
|
|
followed by a call to a validation fucntion which may also annotate the
|
|
|
|
data structures with further information.
|
|
|
|
|
|
|
|
XXX This feels a bit too nested."
|
|
|
|
|
|
|
|
([db description mutation-fn]
|
|
|
|
(transaction! db description mutation-fn identity))
|
|
|
|
|
|
|
|
([db description mutation-fn validation-fn]
|
|
|
|
(reset! db
|
|
|
|
(-> @db
|
|
|
|
(assoc :undo-description description)
|
|
|
|
mutation-fn
|
|
|
|
validation-fn))))
|
2014-12-08 03:48:59 +00:00
|
|
|
|