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-atomprovides an atom backed by local or session storage: https://github.com/alandipert/storage-atom -
platoincrementally 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.