om-local

Sync a cursor in Om's state into localStorage or sessionStorage



Documentation

om-local

-- Work in progress --

Installing

Clojars Project

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

Description

Syncs a cursor in Om's app-state to localState/sessionState in the browser giving you automatic persistent memory across sessions. It helps for example by replacing GarlicJS and allowing you to add similar behavior on data that is not form-related.

Follows same structure as om-sync and om-routes.

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 {:auth {:email "" :pass ""}}))

(defn main-component
    "Submits login with email and password"
    [data owner]
    ...)

(let [tx-chan (chan)
      tx-pub-chan (async/pub tx-chan (fn [_] :txs))]
  (om/root
   (fn [data owner]
     (reify
       om/IRender
       (render [_]
         (om/build local/om-local data
                   {:opts {:view-component main-component 
                           :debug true
                           :local-path [:auth :email]}}))))
   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 allows the user to enter his/her email and password. We want to persist the email across sessions. We can start from a template that includes Om, core.async, and Figwheel for easier development:

lein new figwheel local-example -- --om
cd local-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-local "0.1.1-SNAPSHOT"]] ;; <- Add this

Then by editing src/local-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-local.core :as routes]))

Next we can set the structure of the state:

(defonce app-state (atom {:auth {:email "" :pass ""}}))

Then we write the :view-component that will take and display data:

(defn login [data owner]
  (om/component
   (dom/div nil
            (dom/input 
             #js {:onChange #(om/update! data :email (.. % -target -value) 
                                         :om-local.core/local)
                  :type "email"
                  :value (:email data)
                  :placeholder "Email"})
            (dom/input 
             #js {:onChange #(om/update! data :pass (.. % -target -value))
                  :type "password"
                  :value (:pass data)
                  :placeholder "Password"})
            (dom/button 
             #js {:onClick (fn [_] 
                             (om/update! data [] {:email "" :pass ""} 
                                         :om-local.core/local))}
             "Clear!"))))

Note that we are tagging every update! to the state we want to persist with a namespace qualified keyword, :om-local.core/local. This definitions

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

(let [tx-chan (chan)
      tx-pub-chan (async/pub tx-chan (fn [_] :txs))]
  (om/root
   (fn [data owner]
     (reify
       om/IRender
       (render [_]
         (om/build local/om-local data
                   {:opts {:view-component main-component 
                           :debug true
                           :local-path [:auth :email]}}))))
   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-local
cd om-local
lein cljsbuild once login 

Open examples/login/index.html in your browser.

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.