Skip to content

bensu/om-routes

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

76 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

om-routes

Installing

Clojars Project

(:require [om-routes.core :as routes])

Description

Users expect to use the browser's navigation tools to work everywhere, even inside SPAs. This library binds the browser's url to some cursor in your app-state so that you can model your navigation however you like and it will automatically make it work with the browser's navigation tools.

It follows om-sync's structure. Instead of syncing a cursor's state with an external source, it syncs it with the navbar's url.

It amounts to very little code and is probably not exactly what you need, yet I find it a useful pattern and worth considering.

(defonce app-state (atom {:nav {:last-click nil}}))

(def route [["#" :last-click]
            (routes/make-handler #(update-in % [:last-click] keyword))])

(let [tx-chan (chan)
      tx-pub-chan (async/pub tx-chan (fn [_] :txs))]
  (om/root
   (fn [data owner]
     (reify
       om/IRender
       (render [_]
         (om/build routes/om-routes data
                   {:opts {:view-component view-component
                           :route route
                           :debug true
                           :nav-path :nav}}))))
   app-state
   {:target (. js/document (getElementById "app"))
    :shared {:tx-chan tx-pub-chan}
    :tx-listen (fn [tx-data root-cursor]
                 (put! tx-chan [tx-data root-cursor]))}))

Minimal Example

We are going to code a simple widget that tracks one thing: if the last click was made by a button or a link. We can start from a template that includes Om, core.async, and Figwheel for easier development:

lein new figwheel routes-example -- --om
cd routes-example

We start by adding om-routes to project.clj:

:dependencies [[org.clojure/clojure "1.6.0"]
               [org.clojure/clojurescript "0.0-3126"]
               [figwheel "0.2.5"]
               [org.clojure/core.async "0.1.346.0-17112a-alpha"]
               [sablono "0.3.4"]
               [org.omcljs/om "0.8.8"]
               [om-routes "0.1.1-SNAPSHOT"]] ;; <- Add this

Then by editing src/routes-example/core.cljs and adding some requirements:

(ns routes-example.core
    (:require-macros [cljs.core.async.macros :refer [go]])
    (:require [cljs.core.async :as async :refer [put! chan]]
        	  [om.core :as om :include-macros true]
              [om.dom :as dom :include-macros true]
              [om-routes.core :as routes]))

Next we can set the structure of the state. Everything under :nav will be tracked:

(defonce app-state (atom {:nav {:last-click nil}}))

Now we define how the nave state should be accessed and modified:

(def nav-path :nav)

(defn get-nav [data]
  (get-in data [nav-path :last-click]))

(defn nav-to [view-cursor method]
  (om/update! view-cursor [nav-path :last-click] method :om-routes.core/nav))

Note that we are tagging every update! to the navigation state with a namespace qualified keyword, :om-routes.core/nav. This definitions are not strictly necessary for om-routes but they are good programming practice. Now we define a one-to-one mapping between the navigation state and a url, producing a Bidi handler, route. This will tell om-routes how to transform a url into a navigation map and backwards:

(defn url->state [{:keys [last-click]}]
  {:last-click (keyword last-click)})

(def route [["#" :last-click] (routes/make-handler url->state)])

Note that route matches links that begin with # since we want to specify Fragments of our website and not external resources. All routes should start with #.

Now let's implement the view:

(defn view-component [data owner]
  (om/component
   (dom/div nil
            (dom/h1 nil (case (get-nav data)
                          :button "A button got me here"
                          :link "A link got me here"
                          "Who got me here?"))
            (dom/button #js {:onClick (fn [_] (nav-to data :button))} "Button") 
            (dom/br nil)
            (dom/a #js {:href "#link"} "Link"))))

As you can see, the link also uses # for its href property.

Then we set up a pub-channel for all the transactions. om-routes will listen to those tagged with :om-routes.core/nav. Finally, we wrap the view-component with om-routes by passing it along route, and nav-path as opts:

(let [tx-chan (chan)
      tx-pub-chan (async/pub tx-chan (fn [_] :txs))]
  (om/root
   (fn [data owner]
     (reify
       om/IRender
       (render [_]
         (om/build routes/om-routes data
                   {:opts {:view-component view-component
                           :route route
                           :debug true
                           :nav-path nav-path}}))))
   app-state
   {:target (. js/document (getElementById "app"))
    :shared {:tx-chan tx-pub-chan}
    :tx-listen (fn [tx-data root-cursor]
                 (put! tx-chan [tx-data root-cursor]))}))

Notice adding the :debug option, which defaults to false.

Run the Examples

git clone https://github.com/bensu/om-routes
cd om-routes
lein cljsbuild once track-button

Open examples/track_button/index.html. You can also try a more complicated example by replacing track-button with sorting in the above instructions.

Contributions

Pull requests, issues, and feedback welcome.

License

Copyright © 2015 Sebastian Bensusan

Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.

About

Two way binding between Bidi routes and Om's global state

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published