Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update pushy to allow SPA apps w/ URI fragments. #21

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
:lein-release {:deploy-via :shell
:shell ["lein" "deploy"]}

:profiles {:dev {:dependencies [[secretary "1.2.1"]]
:plugins [[lein-cljsbuild "1.0.5"]
:profiles {:dev {:dependencies [[secretary "1.2.3"]]
:plugins [[lein-cljsbuild "1.1.5"]
[com.cemerick/clojurescript.test "0.3.3"]]

:cljsbuild
Expand Down
52 changes: 40 additions & 12 deletions src/pushy/core.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@
(when (.-parentNode target)
(recur-href (.-parentNode target)))))

(defn- update-history! [h]
(defn- update-history! [h {:keys [use-fragment path-prefix]}]
(doto h
(.setUseFragment false)
(.setPathPrefix "")
(.setUseFragment use-fragment)
(.setPathPrefix (or path-prefix ""))
(.setEnabled true)))

(defn- set-retrieve-token! [t]
Expand All @@ -36,11 +36,17 @@
(str path-prefix token)))
t)

(defn token-transformer []
(-> (TokenTransformer.)
set-retrieve-token!
set-create-url!))

(defn new-history
([]
(new-history (-> (TokenTransformer.) set-retrieve-token! set-create-url!)))
([transformer]
(-> (Html5History. js/window transformer) update-history!)))
(new-history {:token-transformer (token-transformer)}))
([{:keys [token-transformer] :as cfg}]
(-> (Html5History. js/window token-transformer)
(update-history! cfg))))

(defprotocol IHistory
(set-token! [this token] [this token title])
Expand All @@ -55,23 +61,45 @@
(some? (re-matches (re-pattern (str "^" (.-origin js/location) ".*$"))
(str uri))))))

(defn- get-token-from-uri [uri]
(defn- uri->token [uri]
(let [path (.getPath uri)
query (.getQuery uri)]
;; Include query string in token
(if (empty? query) path (str path "?" query))))

(defn- uri-fragment->token [uri]
(.getFragment uri))

(defn setup-fragment-config
"When `use-fragment' is true, configure pushy for this mode."
[{:keys [use-fragment] :as cfg}]
(conj cfg (when use-fragment
{:token-transformer nil
:uri->token-fn uri-fragment->token})))

(defn pushy
"Takes in three functions:
* dispatch-fn: the function that dispatches when a match is found
* match-fn: the function used to check if a particular route exists
* identity-fn: (optional) extract the route from value returned by match-fn"

Additionaly, takes optional keyword arguments:

:identity-fn - Extract the route from value returned by match-fn
:use-fragment - When true, use fragments (hashes) for single-page
applications."
[dispatch-fn match-fn &
{:keys [processable-url? identity-fn]
{:keys [processable-url? identity-fn uri->token-fn]
:or {processable-url? processable-url?
identity-fn identity}}]
identity-fn identity

use-fragment false
path-prefix ""

token-transformer (token-transformer)}
:as cfg}]

(let [history (new-history)
(let [{:keys [uri->token-fn] :as cfg} (setup-fragment-config cfg)
history (new-history cfg)
event-keys (atom nil)]
(reify
IHistory
Expand Down Expand Up @@ -119,7 +147,7 @@
(not (get #{"_blank" "_self"} (.getAttribute el "target")))
;; Bypass dispatch if middle click
(not= 1 (.-button e)))
(let [next-token (get-token-from-uri uri)]
(let [next-token (uri->token-fn uri)]
(when (identity-fn (match-fn next-token))
;; Dispatch!
(if-let [title (-> el .-title)]
Expand Down
38 changes: 25 additions & 13 deletions test/pushy/test/core.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,18 @@
[secretary.core :as secretary :refer-macros [defroute]]
[goog.events :as events]
[cemerick.cljs.test :as t])
(:import goog.history.Html5History))
(:import goog.history.Html5History
goog.Uri))

(secretary/set-config! :prefix "/")
(def test-val (atom false))
(def test-val (atom :fail))

(def history
(pushy/pushy secretary/dispatch!
(fn [x] (when (secretary/locate-route x) x))
identity))

(defroute foo-route "/foo" []
(reset! test-val true))

(defroute bar-route "/bar" []
(reset! test-val true))
(reset! test-val :foo))

(deftest constructing-history
(is (instance? Html5History (pushy/new-history))))
Expand All @@ -31,26 +28,41 @@
(deftest supported-browser
(is (pushy/supported?)))

(deftest fragment-to-token
(is (= "foo" (pushy/uri-fragment->token (Uri. "https://github.com/kibu-australia/pushy#foo")))))

(deftest url-to-token
(is (= "/kibu-australia/pushy" (pushy/uri->token (Uri. "https://github.com/kibu-australia/pushy"))))
(is (= "/kibu-australia/pushy?foo=1" (pushy/uri->token (Uri. "https://github.com/kibu-australia/pushy?foo=1")))))

(deftest test-setup-fragment
(let [non-frag {}
frag {:use-fragment true}]
(is (= non-frag (pushy/setup-fragment-config non-frag)))
(is (nil? (:token-transformer (pushy/setup-fragment-config frag))))
(is (:uri->token-fn (pushy/setup-fragment-config frag)))))

;; event listeners started = dispatch
(deftest ^:async push-state-foo-route
(reset! test-val false)
(reset! test-val :fail)
(secretary/set-config! :prefix "/")
(pushy/start! history)
(pushy/replace-token! history "/foo")
(js/setTimeout
(fn []
(is @test-val)
(is (= :foo @test-val))
(is (nil? (pushy/stop! history)))
(is (= "/foo" (pushy/get-token history)))
(done))
5000))

;; no event listeners started = no dispatch
(deftest ^:async push-state-bar-route
(reset! test-val false)
(pushy/replace-token! history "/bar")
(reset! test-val :fail)
(pushy/replace-token! history "/foo")
(js/setTimeout
(fn []
(is (false? @test-val))
(is (= "/bar" (pushy/get-token history)))
(is (= :fail @test-val))
(is (= "/foo" (pushy/get-token history)))
(done))
5000))
34 changes: 34 additions & 0 deletions test/pushy/test/hash-based.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
(ns pushy.test.hash-based
(:require-macros [cemerick.cljs.test :refer (is deftest done use-fixtures)])
(:require [pushy.core :as pushy]
[secretary.core :as secretary :refer-macros [defroute]]
[goog.events :as events]
[cemerick.cljs.test :as t])
(:import goog.history.Html5History))

(def test-val (atom :fail))

(defn match [x]
(when (secretary/locate-route x) x))

(def history
(pushy/pushy secretary/dispatch! match identity
:use-fragment true))

(defroute baz-hash-route "baz" []
(println "->baz-hash-route")
(reset! test-val :baz))

(deftest ^:async push-state-hash-baz-route
(reset! test-val :fail)
(secretary/set-config! :prefix "#")
(pushy/start! history)
(pushy/replace-token! history "baz")
(js/setTimeout
(fn []
(is (= :baz @test-val))
(is (nil? (pushy/stop! history)))
(is (= "baz" (pushy/get-token history)))
(done))
5000))