Skip to content

Instantly share code, notes, and snippets.

@petterik
Last active March 22, 2016 11:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save petterik/4a80bd5ecba37ed1ea71 to your computer and use it in GitHub Desktop.
Save petterik/4a80bd5ecba37ed1ea71 to your computer and use it in GitHub Desktop.
(ns navigation.example
(:require-macros [natal-shell.components :refer [navigator]]
[navigation.macros :refer [with-om-vars]])
(:require [om.next :as om]))
;;; Helpers for getting a deep ref
(defn react-ref
"Wrapper around react-ref to always turn keywords into strings."
[component ref]
{:pre [(or (keyword? ref) (string? ref))]}
(om/react-ref component (cond-> ref (keyword? ref) str)))
(defn get-ref-in
"Gets react ref in components. Like get-in, but with a component
instead of a map and refs instead of keys. Returns nil if no ref
was found.
Keywords are casted to strings with str."
[component refs]
{:pre [(or (nil? refs) (sequential? refs))]}
(if-let [ref (and component (first refs))]
(recur (react-ref component ref) (next refs))
component))
(defn subquery-in
"Like om/subquery, but can traverse refs like (get-in m [k1 ... kn]).
Defaults to om/get-query of subquery-class if 'x' component is mounted."
[x refs subquery-class]
{:pre [(every? #(or (keyword? %) (string? %)) refs)
(fn? subquery-class)]}
(if (and (om/component? x) (om/mounted? x))
(om/get-query (get-ref-in x refs))
(and (om/iquery? subquery-class)
(om/get-query subquery-class))))
;;; Routes
(def app-route->ui
{:login {:class Login
:factory (om/factory Login)}
:app {:class Main
:factory (om/factory Main)}})
(defn props->route [props]
(or (:app/route props) :login))
;;; Root component
(defui ^:once App
static om/IQuery
(query [this]
;; This query is inspired by the subquery example in:
;; http://anmonteiro.com/2016/02/routing-in-om-next-a-catalog-of-approaches/
;; But since we've got a navigator between our om.next component
;; and this component (App), we'll have to go through the navigator's
;; refs to find our om.next component. See: subquery-in.
(let [route (props->route (when (om/component? this) (om/props this)))
static-query (get-in app-route->ui [route :class])
;; The navigator will contain the ref to the component which we'll
;; want to subquery.
subquery (subquery-in this [:nav route] static-query)
query (cond-> [:app/route]
(some? subquery)
(conj {:route/data subquery}))]
query))
Object
(render-scene [this _ _]
(with-om-vars
this
(let [props (om/props this)
route (props->route props)
factory (get-in app-route->ui [route :factory])]
;; Associate the component with the route
(factory (-> props :route/data (assoc :ref route)))))
(render [this]
;; Wraps the keyword in a string, since (clj->js {:key :val}) => #js {:key "val"}
;; and the ref needs to be ":nav" to work nicely with the rest of the code.
(navigator {:ref (str :nav)
:renderScene #(.render-scene this %1 %2)})))
(ns navigation.macros
(:require [om.next :as om]))
;; Inspired by
;; https://github.com/hugoduncan/navigator-repro/blob/master/src/navigator_repro/macros.clj
(defmacro with-om-vars
"Returns the body of (render [this] ... ) which contains bindings
for various om.next dynamic vars, which is needed when calling
functions returned by (om/factory Class).
This macro allows us to create components outside the render function."
[component & body]
(let [render-with-bindings (get-in om/reshape-map [:reshape 'render])
[_ [this-sym] render-body] (render-with-bindings
`(~'render [~'this]
~@body))]
`(let [~this-sym ~component]
~render-body)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment