re-frame/src/re_frame/handlers.cljs

138 lines
5.2 KiB
Plaintext
Raw Normal View History

(ns re-frame.handlers
(:require [re-frame.db :refer [db]]))
2014-12-08 03:48:59 +00:00
;; -- helpers ------------------------------------------------------------------------------------
(defn- first-in-vector
[v]
(assert (vector? v) (str "Expected a vector event, but got: " v))
(first v))
(defn transaction!
"A helper fucntion for use in event handlers.
Allows a handler to perform an atomic mutation of the atom.
Mutations normally consist of one or more mutations, wrapped by a fucntion,
followed by a call to a validation fucntion."
([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))))
(defn get-in-db
"Direct lookup of arbitrary path in state/db without subscription/reaction.
NOTE: While it is convenient to not have to pass values through function chains,
by definition this also incourages non pure functional style, so resist."
[path-v]
(get-in @db path-v))
2014-12-08 03:48:59 +00:00
;; -- Event Handlers ------------------------------------------------------------------------------
;;
;; In response to user actions, Reagent views produce events (eg: "delete button clicked")
;;
;; The on-click for a button on a item, might look like this:
;; :onclick #(dispatch [:delete-item item-id])
;;
;; 'dispatch' is defined below and it is called for ALL events. Every single one.
;; dispatch takes one parameter: a vector. The first element of the vector identifies the event
;; and the other elements are event "paramters".
;;
;; Every event has an associated handler (function).
;;
;; Collectively, these handlers provide the 'control layer' in the architecture.
;;
;; They are the only part of the application allowed to mutate the db. At their simplest, they
;; perform trivial CRUD actions. More complicated ones impose some control over the mutations, much
;; like "stored proceedures" might enforce business rules in a traditional database. But handlers
;; might also need to talk to a server, requesting additional data (which might async be placed back in
;; 'db' etc).
;;
;; Handler functions must have this signature:
;; (db event-vector) -> db
;;
;; When they are called, they are given the db to mutate as the first parameter, and the full
;; event vector is the 2nd parameter.
;;
;; Note: The 1st item in an event-vector is the key which identifies the event. Subsequent
;; items in the vector are effectively "parameters" for the event.
;;
;; Example of event-vector: [:delete-pod "a parameter" "another"]
;; Another: [[:multi :part "event" :key] "a parameter" "another"]
;;
;; Event handlers are registered.
;;
;; The following maps from event-ids to handler fucntions. See also 'register-handler'.
(def ^:private event-id->handler-fn (atom {}))
;; register a handler for a paraticular event
(defn register-handler
[event-id handler-fn]
(if (contains? @event-id->handler-fn event-id)
(println "Warning: overwritting an event-handler" event-id)) ;; TODO: more generic logging
(swap! event-id->handler-fn
assoc event-id handler-fn))
;; reagent components call this function when they want to "send" an event.
(defn dispatch
2014-12-08 03:48:59 +00:00
[event-v] ;; something like [:delete-item 42]
(let [event-id (first-in-vector event-v)
handler-fn (get @event-id->handler-fn event-id)]
(assert (not (nil? handler-fn)) (str "No event handler registered for event: " event-id ))
(handler-fn db event-v)))
2014-12-08 03:48:59 +00:00
;; -- Subscriptions ---------------------------------------------------------------------------
;;
;; Reagent components call 'subscribe' when they wish to observe some of the data in the db.
;;
;; 'subscribe' returns a reagent/atom which is updated whenever (any of) the db changes. So
;; subscribe effectively delivers a stream of updates in something of an FRP sense.
;; (the updates Sometimes
;; the update
;;
;; Typically, components are only interested in part of the overall db. So, when
;; calling 'subscribe', you give a 'subsciption-key' which identifies the part tht you want.
;;
;; You register handlers for keys via 'register-subscription'
;;
;; Subscription Handler functions must have this signature:
;; (db key-vector) -> reagent/atom
;;
;; XXX Note here about 'reaction' and the need for dispose.
;;
;; Example of subscription-key: [:items :from 20 :to 30]
;; Another: [[:multi :part :key] "a parameter" "another"]
;;
;; The following map from subscription-ids to handler fucntions.
(def ^:private subscription-key->handler-fn (atom {}))
(defn register-subscription
[key-v handler-fn]
(if (contains? @subscription-key->handler-fn key-v)
(println "Warning: overwritting a subscription-handler: " key-v)) ;; TODO: more generic logging
(swap! subscription-key->handler-fn assoc key-v handler-fn))
2014-12-08 03:48:59 +00:00
(defn subscribe
"returns a reagent/reaction which observes a part of the "
[v]
(let [key-v (first-in-vector v)
handler-fn (get @subscription-key->handler-fn key-v)]
(assert (not (nil? handler-fn)) (str "No subscription handler registered for key: " key-v))
(handler-fn db v)))
2014-12-08 03:48:59 +00:00