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
}