A idiomatic ClojureScript interface to HTML5 Storage.
1. Rationale
This library is intended as a thin wrapper around the browser’s storage API for making it more Clojurey. Furthermore, it extends the Storage type for treating storage like a transient map for inserting and retrieving ClojureScript data structures.
The serialization is done via pr-str
and it uses the reader for deserializing strings
into ClojureScript or JavaScript data structures.
1.1. Alternatives
-
storage-atom
provides an atom backed by local or session storage: https://github.com/alandipert/storage-atom -
plato
incrementally persist atom state to local storage: https://github.com/eneroth/plato
2. Installation
The simplest way to use hodgepodge
in a Clojure project is by including
it as a dependency in your project.clj:
[hodgepodge "0.1.3"]
3. Storages
At the moment hodgepodge
supports local and session storage.
(require '[hodgepodge.core :refer [local-storage
session-storage]])
4. Low-level API
hodgepodge
has an API that mimics that of web storage, which can be used for writing and reading raw strings.
(require '[hodgepodge.core :refer [local-storage
get-item
set-item
remove-item
clear!
length]])
(clear! local-storage)
(length local-storage)
;; => 0
(set-item local-storage "foo" "bar")
(length local-storage)
;; => 1
(get-item local-storage "foo")
;; => "bar"
(remove-item local-storage "foo")
(length local-storage)
;; => 0
get-item
also supports passing a default argument to return when the given key is missing.
(require '[hodgepodge.core :refer [local-storage
get-item
set-item
clear!]])
(clear! local-storage)
(get-item local-storage "foo" 42)
;; => 42
(set-item local-storage "foo" "bar")
(get-item local-storage "foo" 42)
;; => "bar"
5. Transient storage
Besides the low-level, raw-string API, storages implement several ClojureScript core protocols. The keys and values
that are inserted in the storage are serialized with pr-str
and deserialized with the reader.
Storages can be treated as an ITransientAssociative
and ITransientMap
, thus supporting assoc!
and dissoc!
. They
also implement ICounted
so you can use count
on them.
(require '[hodgepodge.core :refer [local-storage clear!]])
(clear! local-storage)
(def val {:bar 42 :timestamp (js/Date.)})
(assoc! local-storage :foo val)
(count local-storage)
;; => 1
(dissoc! local-storage :foo)
(count local-storage)
;; => 0
The implementation of the ILookup
protocol let’s us extract values from the storage.
(require '[hodgepodge.core :refer [local-storage clear!]])
(clear! local-storage)
(assoc! local-storage :foo {:bar 42})
(:foo local-storage)
;; => {:bar 42}
(dissoc! local-storage :foo)
(count local-storage)
(get local-storage :foo :missing)
;; => :missing
Storage objects can be transformed into persistent data structures calling persistent!
on them.
(require '[hodgepodge.core :refer [local-storage clear!]])
(clear! local-storage)
(assoc! local-storage :foo {:bar 42})
(= (persistent! local-storage)
{:foo {:bar 42}})
;; => true
5.1. Storing and retrieving custom data types
Since storage serialization is done with pr-str
, we can make a custom type serializable
implementing the cljs.core/IPrintWithWriter
protocol and making it output a tagged literal.
For a contrived example, let’s implement a Action
type which is of a ceratin kind and
has a payload associated with it.
(deftype Action [kind payload]
IEquiv
(-equiv [_ other]
(and (instance? Action other)
(= kind (.-kind other))
(= payload (.-payload other)))))
First, we are going to make its representation a tagged literal.
(extend-type Action
IPrintWithWriter
(-pr-writer [a writer _]
(-write writer (str "#action \""
(pr-str {:kind (.-kind a)
:payload (.-payload a)})
"\""))))
(enable-console-print!)
(print (Action. :write-code {:language :clojurescript}))
; #action "{:kind :write-code, :payload {:language :clojurescript}}"
Now that our type is serializable we can make it deserializable by writing a function
that is able to read its literals and create an instance of Action
. After doing this
we will extend the reader to be able to read our custom type’s tagged literals.
(require '[cljs.reader :as reader])
(defn read-action
[a]
(let [values (reader/read-string a)]
(Action. (:kind values) (:payload values))))
(reader/register-tag-parser! "action" read-action)
Now we’re able to store and retrieve actions from storages and to use them as keys.
(require '[hodgepodge.core :as h])
(def a (Action. :write-code {:language :clojure}))
(assoc! h/local-storage :action a)
(assert (= a (:action h/local-storage)))
(assoc! h/local-storage a :action)
(assert (= :action (get h/local-storage a)))
6. License
Licensed under the BSD 2-clause license. Copyright (c) 2014, Alejandro Gómez.