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

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.