Skip to content

Instantly share code, notes, and snippets.

@tomconnors
Last active December 21, 2015 18:26
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tomconnors/c1cceaae84fd059e37a3 to your computer and use it in GitHub Desktop.
Save tomconnors/c1cceaae84fd059e37a3 to your computer and use it in GitHub Desktop.
An attempt at adding routes to the Om Components, Identity, and Normalization tutorial
(ns om-tutorial.core
(:require [goog.dom :as gdom]
[om.next :as om :refer-macros [defui]]
[om.dom :as dom]))
(enable-console-print!)
(def init-data
{:list/one [{:name "John" :points 0}
{:name "Mary" :points 0}
{:name "Bob" :points 0}]
:list/two [{:name "Mary" :points 0 :age 27}
{:name "Gwen" :points 0}
{:name "Jeff" :points 0}]
;; ADDED:
:session/route :route-1})
;; -----------------------------------------------------------------------------
;; Parsing
(defmulti read om/dispatch)
(defn get-people [state key]
(let [st @state]
(into [] (map #(get-in st %)) (get st key))))
(defmethod read :list/one
[{:keys [state] :as env} key params]
{:value (get-people state key)})
(defmethod read :list/two
[{:keys [state] :as env} key params]
{:value (get-people state key)})
;; ADDED: (gets the current route; :route-1 or :route-2)
(defmethod read :session/route
[{:keys [state] :as env} key params]
{:value (-> state deref key)})
;; ADDED: perform a read for a child query.
(defmethod read :child
[{:keys [parser query] :as env} k params]
{:value (parser env query)})
(defmulti mutate om/dispatch)
(defmethod mutate 'points/increment
[{:keys [state]} _ {:keys [name]}]
{:action
(fn []
(swap! state update-in
[:person/by-name name :points]
inc))})
(defmethod mutate 'points/decrement
[{:keys [state]} _ {:keys [name]}]
{:action
(fn []
(swap! state update-in
[:person/by-name name :points]
#(let [n (dec %)] (if (neg? n) 0 n))))})
;; ADDED: (sets route to either :route-1 or :route-2)
(defmethod mutate 'session/set-route
[{:keys [state]} _ {:keys [name]}]
{:action (fn [] (swap! state assoc :session/route name))})
;; -----------------------------------------------------------------------------
;; Components
(defui Person
static om/Ident
(ident [this {:keys [name]}]
[:person/by-name name])
static om/IQuery
(query [this]
'[:name :points :age])
Object
(render [this]
(println "Render Person" (-> this om/props :name))
(let [{:keys [points name] :as props} (om/props this)]
(dom/li nil
(dom/label nil (str name ", points: " points))
(dom/button
#js {:onClick
(fn [e]
(om/transact! this
`[(points/increment ~props)]))}
"+")
(dom/button
#js {:onClick
(fn [e]
(om/transact! this
`[(points/decrement ~props)]))}
"-")))))
(def person (om/factory Person {:keyfn :name}))
(defui ListView
Object
(render [this]
(println "Render ListView" (-> this om/path first))
(let [list (om/props this)]
(apply dom/ul nil
(map person list)))))
(def list-view (om/factory ListView))
;; BEGIN ADDED SECTION
;; This view functions as "page 1"
(defui RouteOne
static om/IQuery
(query [this]
(let [subquery (om/get-query Person)]
`[{:list/one ~subquery}]))
Object
(render [this]
(let [{:keys [list/one]} (om/props this)]
(dom/div nil
(dom/h2 nil "List A")
(list-view one)))))
;; and this is "page 2"
(defui RouteTwo
static om/IQuery
(query [this]
(let [subquery (om/get-query Person)]
`[{:list/two ~subquery}]))
Object
(render [this]
(let [{:keys [list/two]} (om/props this)]
(dom/div nil
(dom/h2 nil "List B")
(list-view two)))))
(def route->component
{:route-1 RouteOne
:route-2 RouteTwo})
(def route->factory
(zipmap (keys route->component)
(map om/factory (vals route->component))))
(def route->query
(zipmap (keys route->component)
(map om/get-query (vals route->component))))
;; END ADDED SECTION
(defui RootView
static om/IQuery
(query [this]
;; UPDATED - default to :route-1 as first route
(let [subquery (route->query :route-1)]
;; union query for the current :session/route
;; and the query for the data needed by that route
[:session/route {:child subquery}]))
Object
(render [this]
(println "Render RootView")
;; UPDATED:
(let [props (om/props this)
{:keys [session/route child]} props]
(dom/div
nil
(dom/div
nil
(dom/button
#js {:onClick (fn [e]
;; when "Switch route" button is clicked,
;; update the query for the root component
;; to include the data for the new route
;; (and exclude the data for the old route)
;; and transact to set the :session/route
;; in the app-state atom.
(let [new-route (->> [:route-1 :route-2]
(remove #(= % route))
first)
q [:session/route {:child (route->query new-route)}]]
(om/set-query! this {:query q})
(om/transact!
this
`[(session/set-route {:name ~new-route})])))}
(str "Switch Route away from " route)))
;; render the component for the current route
((route->factory route) child)))))
;; ADDED:
;; Explicitly normalize the app-db
;; Using the union of the query fo every route.
(def universal-query
(into [:session/route] (mapcat identity (vals route->query))))
(def init-data-db
(-> (om/tree->db universal-query init-data true)
;; this key is probably harmless enough but I think it's cleaner
;; to leave it out of the :state I pass to om
(dissoc :om.next/tables)))
(def reconciler
(om/reconciler
{:state (atom init-data-db) ;; pass :state as an atom so om doesn't normalize it
:parser (om/parser {:read read :mutate mutate})}))
;; wrapped in fn for reloading w/ figwheel
(defn go [] (om/add-root! reconciler
RootView (gdom/getElement "root-mount-point")))
@tomconnors
Copy link
Author

Note that this code does not work. Navigation to :route-2 fails.

@tomconnors
Copy link
Author

With the latest update, this code renders both routes correctly but mutations fail.

om.next/full-query is not working for any components other than the RootView. Looking at the indexer's :class-path->query map, the expected path (om-tutorial.core/RootView om-tutorial.core/RouteTwo om-tutorial.core/Person) is missing, but (om-tutorial.core/RootView om-tutorial.core/Person) is present.

@tomconnors
Copy link
Author

Now this code works.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment