shoreleave/shoreleave-browser0.3.0A smarter client-side with ClojureScript : Shoreleave's enhanced browser utilities dependencies
dev dependencies
| (this space intentionally left almost blank) | |||||||||
An idiomatic interface to Blobs | (ns shoreleave.browser.blob) | |||||||||
BlobsHTML5 File API supports the creation of Blobs. Blobs allow you to take arbitrary text (like functions) and create file-like
objects, that get their own unique URL wuth a This is useful if you're making an app that wants to use on-demand assets, or you need to build something like embedded web workers. It also comes in handy if you want to build dynamic content on the fly, like streaming images. The Blob API is now a stable spec in HTML5. To create blobs, you pass a vector of file contents (or parts) to | ||||||||||
(defn- window-url-prop [] (or (.-URL js/window) (.-webkitURL js/window))) | ||||||||||
Build a new Blob object, but don't muck with the args. This is for low-level interop stuff - when needed. | (defn raw-blob ([file-parts] (js/Blob. file-parts)) ([file-parts prop-bag] (js/Blob. file-parts prop-bag))) | |||||||||
Build the file-contents into a Blob and return it. Optionally set the content-type via a string | (defn blob ([file-parts] (js/Blob. (clj->js file-parts))) ([file-parts content-type-str] (js/Blob. (clj->js file-parts) (js-obj "type" content-type-str)))) | |||||||||
Create a unique object URL (ala | (defn object-url! [file-or-blob] (let [url (window-url-prop)] (when url (.createObjectURL url file-or-blob)))) | |||||||||
(defn revoke-object-url! [obj-url] (let [url (window-url-prop)] (when url (.revokeObjectURL url obj-url)))) | ||||||||||
An idiomatic interface to cookies | (ns shoreleave.browser.cookies (:require [goog.net.Cookies :as gCookies] [goog.string :as gstr])) | |||||||||
Cookie supportShoreleave's cookie support is built upon Closure's Cookies. The base object is extended to support the following calls:
| ||||||||||
(declare as-hash-map) | ||||||||||
TODO: Consider making Cookies extend IWatchable | ||||||||||
(extend-type goog.net.Cookies ILookup (-lookup ([c k] (-lookup c k nil)) ([c k not-found] ;gstr/urlDecode (let [v (.get c (name k) not-found)] (if (string? v) (gstr/urlDecode v) v)) #_(.get c (name k) not-found))) ISeqable (-seq [c] (map vector (.getKeys c) (.getValues c))) ICounted (-count [c] (.getCount c)) IFn (-invoke ([c k] (-lookup c k)) ([c k not-found] (-lookup c k not-found))) ITransientCollection (-persistent! [c] (as-hash-map c)) ;(-conj! [c v] nil) ITransientAssociative (-assoc! [c k v & opts] (when-let [k (and (.isValidName c (name k)) (name k))] (let [{:keys [max-age path domain secure?]} (apply hash-map opts)] (.set c k v max-age path domain secure?)))) ITransientMap (-dissoc! [c k & opts] (when-let [k (and (.isValidName c (name k)) (name k))] (let [{:keys [path domain]} (apply hash-map opts)] (.remove c k path domain)))) IAssociative (-assoc [c k v] (-assoc (-persistent! c) k v)) (-contains-key? [c k] (.containsKey c (name k))) ;IPrintable ;(-pr-seq [c opts] ; #_(let [pr-pair (fn [keyval] (pr-sequential pr-seq "" " " "" opts keyval))] ; (pr-sequential pr-pair "{" ", " "}" opts c)) ; (-pr-seq (-persistent! c) opts)) IPrintWithWriter (-pr-writer [c writer opts] (-write writer (-persistent! c))) ;; TODO: using the persistent version here might be a bad idea IHash (-hash [c] (-hash (-persistent! c)))) | ||||||||||
(def cookies (goog.net.Cookies. js/document)) | ||||||||||
(defn as-hash-map ([] (as-hash-map cookies)) ([cks] (zipmap (.getKeys cks) (.getValues cks)))) | ||||||||||
Returns a boolean, true if cookies are currently enabled for the browser | (defn cookies-enabled? ([] (cookies-enabled? cookies)) ([cks] (.isEnabled cks))) | |||||||||
(defn empty! [cks] (.clear cks)) | ||||||||||
An idiomatic interface to browser history | (ns shoreleave.browser.history (:require [goog.events :as gevents] [goog.History :as ghistory] [goog.history.EventType :as history-event] [goog.history.Html5History :as history5])) | |||||||||
This is the history object - the interface the browser's history You can initialize your own history object, but you're encouraged to us the one provided | (declare history) | |||||||||
Navigation EventsAdding History support to your application allows you to correctly handle location-bar state and enables correct usage of a browser's navigation buttons (like the back button). Every point of "navigation" within your app can be added to the browsers history, allowing you to go backwards and forwards in that history History events are packaged up as a map with the keys:
| ||||||||||
Add a function to be called when a navigation event happens. The function should accept a single map, with keys :token, :type, :navigation? | (defn navigate-callback ([callback-fn] (navigate-callback history callback-fn)) ([hist callback-fn] (gevents/listen hist history-event/NAVIGATE (fn [e] (callback-fn {:token (keyword (.-token e)) :type (.-type e) :navigation? (.-isNavigation e)}))))) | |||||||||
Initialize the browser's history, with HTML5 API support if available | (defn init-history [] (let [history (if (history5/isSupported) (goog.history.Html5History.) (goog.History.))] (.setEnabled history true) (gevents/unlisten (.-window_ history) (.-POPSTATE gevents/EventType) ; This is a patch-hack to ignore double events (.-onHistoryEvent_ history), false, history) history)) | |||||||||
Get the current token/url string in the browser history | (def history (init-history)) (defn get-token [hist] (.getToken hist)) | |||||||||
Add a new token to the brower's history. This will trigger a nvaigate event | (defn set-token! [hist tok] (.setToken hist tok)) | |||||||||
(defn replace-token! [hist tok] (.replaceToken hist tok)) | ||||||||||
Raw access to the HTML5 History APIThis is advantageous when you want to use the stateobj for partial view or data caching For most applications this is not needed (nor is it advised). It's useful for storing remote URLs the page needed, or small pieces of page specific state that need to be restored for the page to be functional. | ||||||||||
(defn push-state [hist state-map] (let [{:keys [state title url] :or {state nil title js/document.title}} state-map] (apply js/window.history.pushState (map clj->js [state title url])) (.dispatchEvent hist (goog.history.Event. url false)))) | ||||||||||
An idiomatic interface to the browser's local storage | (ns shoreleave.browser.storage.localstorage (:require [cljs.reader :as reader] [goog.storage.mechanism.HTML5LocalStorage :as html5ls] [shoreleave.browser.storage.webstorage])) | |||||||||
WatchersIn most applications, you want to trigger actions when data is changed. To support this, Shoreleave's local storage use IWatchable and maintains the watchers in an atom. | ||||||||||
(def ls-watchers (atom {})) | ||||||||||
`localStorage` supportFor general information on localStorage, please see Mozilla's docs Shoreleave's localStorage support is built against Closure's interface The extension supports the following calls:
Using localStorage in Pub/SubThe apprpriate IWatchable support is attached to Google's HTML5LocalStorage to allow it to participate in Shoreleave's pub/sub system To enable it, you need to | ||||||||||
(extend-type goog.storage.mechanism.HTML5LocalStorage IWatchable (-notify-watches [ls oldval newval] (doseq [[key f] @ls-watchers] (f key ls oldval newval))) (-add-watch [ls key f] (swap! ls-watchers assoc key f)) (-remove-watch [ls key] (swap! ls-watchers dissoc key))) | ||||||||||
Get the browser's localStorage UsageYou'll typically do something like: | (defn storage [] (goog.storage.mechanism.HTML5LocalStorage.)) | |||||||||
Much like how you can easily get "cookies/cookies" you can get "localstorage/localstorage" | (def localstorage (storage)) | |||||||||
An idiomatic interface to the browser's session storage | (ns shoreleave.browser.storage.sessionstorage (:require [cljs.reader :as reader] [goog.storage.mechanism.HTML5SessionStorage :as html5ss] [shoreleave.browser.storage.webstorage])) | |||||||||
WatchersIn most applications, you want to trigger actions when data is changed. To support this, Shoreleave's session storage use IWatchable and maintains the watchers in an atom. This is identical to techniques used in local storage. | ||||||||||
(def ss-watchers (atom {})) | ||||||||||
`sessionStorage` supportFor general information on sessionStorage, please see Mozilla's docs Shoreleave's sessionStorage support is built against Closure's interface The extension supports the following calls:
Using sessionStorage in Pub/SubThe apprpriate IWatchable support is attached to Google's HTML5SessionStorage to allow it to participate in Shoreleave's pub/sub system To enable it, you need to | ||||||||||
(extend-type goog.storage.mechanism.HTML5SessionStorage IWatchable (-notify-watches [ss oldval newval] (doseq [[key f] @ss-watchers] (f key ss oldval newval))) (-add-watch [ss key f] (swap! ss-watchers assoc key f)) (-remove-watch [ss key] (swap! ss-watchers dissoc key))) | ||||||||||
Get the browser's sessionStorage UsageYou'll typically do something like: | (defn storage [] (goog.storage.mechanism.HTML5SessionStorage.)) | |||||||||
Much like how you can easily get "cookies/cookies" you can get "sessionstorage/sessionstorage" | (def sessionstorage (storage)) | |||||||||
An idiomatic interface to the browser's storage mechanisms (local and sessions) | (ns shoreleave.browser.storage.webstorage (:require [cljs.reader :as reader] [goog.storage.mechanism.HTML5WebStorage :as html5webstorage] [goog.iter :as g-iter])) | |||||||||
Google Closure attaches a common prototype to all browser storage systems called, | ||||||||||
WebStorage supportFor general information on localStorage, please see the docs in Shoreleave's generic storage support is built against Closure's interface The extension supports the following calls:
Using storage in Pub/SubThere is PubSub support for the specific storage types. Please see the details in those files. You'll need to require them directly to get support. | ||||||||||
(defn storage-keys [ls] (g-iter/toArray (.__iterator__ ls true))) (defn storage-values [ls] (g-iter/toArray (.__iterator__ ls false))) (defn as-hash-map ([storage] (zipmap (storage-keys storage) (storage-values storage)))) | ||||||||||
(extend-type goog.storage.mechanism.HTML5WebStorage ILookup (-lookup ([ls k] (-lookup ls k nil)) ([ls k not-found] (let [read-value (if-let [v (not-empty (.get ls (name k)))] v (pr-str not-found))] (reader/read-string read-value)))) ISeqable (-seq [ls] (map vector (storage-keys ls) (storage-values ls))) ICounted (-count [ls] (.getCount ls)) IFn (-invoke ([ls k] (-lookup ls k)) ([ls k not-found] (-lookup ls k not-found))) ITransientCollection (-persistent! [ls] (as-hash-map ls)) ;(-conj! [c v] nil) ITransientAssociative (-assoc! [ls k v] (let [old-val (-lookup ls k)] (.set ls (name k) (pr-str v)) (-notify-watches ls {k old-val} {k v}) ls)) ITransientMap (-dissoc! [ls k] (do (.remove ls (name k)) ls)) ;IPrintable ;(-pr-seq [ls opts] ; #_(let [pr-pair (fn [keyval] (pr-sequential pr-seq "" " " "" opts keyval))] ; (pr-sequential pr-pair "{" ", " "}" opts ls)) ; (-pr-seq (-persistent! ls) opts)) IPrintWithWriter (-pr-writer [ls writer opts] (let [pers-st (-persistent! ls)] (-write writer (-persistent! ls))))) | ||||||||||
Clear the storage | (defn empty! [ls] (.clear ls)) | |||||||||