Last active
December 21, 2015 18:26
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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"))) |
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.
Now this code works.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Note that this code does not work. Navigation to :route-2 fails.