Om Notes - taylorSando/om GitHub Wiki
Top Level View
App, View, Remotes
Components are Views Can talk about them before even talking about state
They are javascript classes, defined by defui. Has a single Object method called render, which is used by React
om factory takes component and returns a function which takes two arguments, a props argument, and a children argument.
Adding state Global state is bad, and hard to manage
Components are decoupled from state, they only request data, or make specific write requests. Mediated by the Reconciler.
Protocol of Client Server (READ/MUTATE) Data Format: EDN
Dealing with this protocol is mediated by Parsing.
Parser Takes a read, mutate argument
The function returned by parser is a function that takes an environment, and query expression. It evaluates it using the read and mutate functions.
Query Expressions and their Grammar (advanced)
Read and Mutate functions should be defined by a multi function so they can dispatch certain keys.
Read Fn (env, selector, params) selector is a vector
Read Fn returns a map
Should return a {:value :val}
Write Fn (env, selector, params) Returns a map {:value {:keys [] :tempids {}} :action (fn [] (update state))}
keys specifies what read operations should happen after mutation
action is a thunk that updates the state
Changing Components to handle the parser logic
om.next/IQuery (query [this])
A query expression that defines what the component needs from the state.
Is defined as static because you don't necessarily need an instance of the component for it to run, can be static (as in on a class) function.
(om/transact! reconciler/component expression)
om/IQueryParams (params [this])
Returns a map of data
{:start 0 :end 10}
Can be referred to in query
(query [this]), i.e. ?start ?end
Any values returned from query will show up in render's prop
(om/set-params! class/component new-params)
Will result in new query params on the components/classes
Ident (ident [this params])
(ident [this {:keys [eid]}] [:some/key eid])
Parsing Logic
Found in the doc string of the Parser Implementation
When passing env, query-expression, there is an optional 3rd argument that is the remote keyword. It will potentially return something the remote keyword in the map that is returned from the read function.
What it returns is by default the query expression that was passed into the read function.
(defmethod read :woz/noz
[{:keys [state]} k params]
(if-let [v (get @state k)]
{:value v :remote true} ;; local read AND remote read
{:remote true})) ;; no cached locally, must read remote
(deftest test-value-and-remote
(let [st (atom {:woz/noz 1})]
;; The value is returned normally
(is (= (p {:state st} [:woz/noz]) {:woz/noz 1}))
;; The remote returns the selector
(is (= (p {:state st} [:woz/noz] :remote) [:woz/noz]))))
If you specify a dispatch key that does not have a remote attached to it, it will just return an empty vector.
Should go through the difference between calls and props. read calls
'(:some.fn/key {:x 5 :y 7})
;; The map is considered parameters to the function
'({:some.fn/key [:selector-1 :selector-2]} {:x 5 :y 7})
;; Here a map is in the function position.
;; The key is the function to call, :some.fn/key
;; The query is, [:selector-1 :selector-2]
;; The parameters, the second argument to the function call, {:x 5 :y 7}
Parameterized Join
(defmethod read :now/wow
[{:keys [state query]} k params]
{:value {:query query :params params}})
(deftest test-parameterized-join
(let [st (atom {:foo/bar 1})]
(is (= (p {:state st} '[({:now/wow [:a :b]} {:slice [10 20]})])
'{:now/wow {:query [:a :b] :params {:slice [10 20]}}}))))
Mutation Actions only occur when a remote key is not specified
(defmethod mutate 'mutate!
[{:keys [state]} k params]
{:value {:keys []}
:action #(swap! state update-in [:count] inc)} )
(deftest test-remote-does-not-mutate
(let [st (atom {:count 0})
_ (p {:state st} '[(mutate!)])
_ (p (:state st) '[(mutate!)] :remote)]
(is (= @st {:count 1}))))
;; The first mutate! is what results in the atom being updated, because the action thunk is ran.
;; The second mutate! does not result in the action being ran.
Remotes
gather-sends -> Gather everything returns by the remotes
send
The remote function that handles sends. The first argument is the arguments map resulting from parsing all the remotes. The second argument is the callback function. This callback function takes what is returned by the server, merges the results, and then re-renders the app.
{:remotes [:remote1 :remote2 :remote3]
:send (fn [send-map merge-callback])
;; merge-callback -> (fn [server-data] (merge! server-data)), which gets merged back
;; merge! calls merge, and then migrate behind the scenes
:merge (fn [reconciller state result])
;; Returns a map with keys [:keys :next :tempids]
;; keys are designed to be re-read by the renderer
:migrate (fn [next root-query tempids id-key-fn])
;; The result of migrate is reset! back into the state
;; If there is no migrate, then next is reset back into the state
}