Skip to content

Commit

Permalink
Pushy returns reify instance which wraps goog.history.Html5History
Browse files Browse the repository at this point in the history
Previously pushy was constructing instance of goog.history.Html5History
before user initialized library.

Update API to use IHistory protocol which implements wrapper
methods for goog.history.Html5History and `start!` and `stop!` methods
for pushy event listeners

Test suite also passes!
  • Loading branch information
wavejumper committed Apr 28, 2015
1 parent 0fdb417 commit 6be68dd
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 144 deletions.
76 changes: 45 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@

A Clojurescript library for quick and easy HTML5 pushState.

What it does:

* Initializes `goog.history.HTML5History`
* Adds an event listener to all `click` events, which dispatches on all matched routes. Bypasses Alt, Shift, Meta, Ctrl keys as well as middle clicks.

## Install

[![Clojars Project](http://clojars.org/kibu/pushy/latest-version.svg)](http://clojars.org/kibu/pushy)
Expand All @@ -16,66 +11,85 @@ What it does:

### Setup

You can initialize pushState by calling the `push-state!` function.
You can construct a new instance by calling the `pushy` function.

This takes in two arguments:
`pushy` takes in two arguments:

* `dispatch` fn: gets called when there is a match
* `match` fn: checks if the path matches any routes defined.

Optionally, you can pass in an `identity` fn which parses and returns the route based on the result of the `match` fn

### Event listeners

You can start the event listeners with the `start!` method.

This adds an event listener to all `click` events, which dispatches on all matched routes.
Bypasses on Alt, Shift, Meta, Ctrl keys and middle clicks.

The `stop!` method will tear down all event listeners of the pushy instance.

pushy should work with any routing library.
### Routing libraries

pushy should work with any routing library:

[Secretary](https://github.com/gf3/secretary)

```clojure
(ns foo.core
(:require [secretary.core :as secretary :include-macros true :refer [defroute]]
[pushy.core :as pushy :refer [push-state!]))
(ns foo.core
(:require [secretary.core :as secretary :include-macros true :refer-macros [defroute]]
[pushy.core :as pushy]))

(secretary/set-config! :prefix "/")
(secretary/set-config! :prefix "/")

(defroute index "/" []
(.log js/console "Hi"))
(defroute index "/" []
(.log js/console "Hi"))

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

;; Start event listeners
(pushy/init! history)
```

[Bidi](https://github.com/juxt/bidi)

```clojure
(ns foo.core
(:require [bidi.bidi :as bidi]
[pushy.core :as pushy :refer [push-state!]))
(ns foo.core
(:require [bidi.bidi :as bidi]
[pushy.core :as pushy))

(def state (atom {}))

(def state (atom {}))
(def app-routes
["/" {"foo" :foo}])

(def app-routes
["/" {"foo" :foo}])
(defn set-page! [match]
(swap! state assoc :page match))

(defn set-page! [match]
(swap state assoc :page match))
(def history
(pushy/pushy set-page! (partial bidi/match-route app-routes)))

(push-state! set-page! (partial bidi/match-route app-routes))
(pushy/init! history)
```

### set-token!
### goog.history.HTML5History methods

It is also possible to set the history state by calling the `set-token!` function. This will also call the `dispatch` fn on a successfully matched path.
You can set the history state manually by calling the `set-token!` method. This will call the `dispatch` fn on a successfully matched path.

Example:

```clojure
(set-token! "/foo")
(set-token! history "/foo")

(get-token history)
;; => /foo
```

Likewise, you can call `replace-token!` which will also call the `dispatch` fn, but replaces the current history state without affecting the rest of the history stack.
Likewise, you can call `replace-token!` which will also call the `dispatch` fn and replace the current history state without affecting the rest of the history stack.

## License

Copyright © 2014

Distributed under the Eclipse Public License either version 1.0 or (at
your option) any later version.
Distributed under the Eclipse Public License either version 1.0
12 changes: 3 additions & 9 deletions project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
:url "http://www.eclipse.org/legal/epl-v10.html"}

:dependencies [[org.clojure/clojure "1.6.0"]
[org.clojure/clojurescript "0.0-2356"]]
[org.clojure/clojurescript "0.0-3211"]]

:aliases {"deploy" ["do" "clean," "deploy" "clojars"]
"test" ["do" "clean," "with-profile" "dev" "cljsbuild" "test"]}
Expand All @@ -14,18 +14,12 @@
:shell ["lein" "deploy"]}

:profiles {:dev {:dependencies [[secretary "1.2.1"]]

:plugins [[lein-cljsbuild "1.0.3"]
[com.cemerick/clojurescript.test "0.3.1"]]
:plugins [[lein-cljsbuild "1.0.5"]
[com.cemerick/clojurescript.test "0.3.3"]]

:cljsbuild
{:test-commands
{"unit" ["phantomjs" :runner
"window.literal_js_was_evaluated=true"
"test/vendor/es5-shim.js"
"test/vendor/es5-sham.js"
"test/vendor/history-shim.js"
"test/vendor/console-polyfill.js"
"target/unit-test.js"]}
:builds
{:test {:source-paths ["src" "test"]
Expand Down
165 changes: 84 additions & 81 deletions src/pushy/core.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@
(recur-href (.-parentNode target)))))

(defn- update-history! [h]
(.setUseFragment h false)
(.setPathPrefix h "")
(.setEnabled h true)
h)
(doto h
(.setUseFragment false)
(.setPathPrefix "")
(.setEnabled true)))

(defn- set-retrieve-token! [t]
(set! (.. t -retrieveToken)
Expand All @@ -35,86 +35,89 @@
(str path-prefix token)))
t)

(def transformer
(-> (TokenTransformer.) set-retrieve-token! set-create-url!))

(def history
(-> (Html5History. js/window transformer) update-history!))

(defn set-token!
"Sets the history state"
([token]
(. history (setToken token)))
([token title]
(. history (setToken token title))))

(defn replace-token!
"Replaces the current history state without affecting the rest of the history stack"
([token]
(. history (replaceToken token)))
([token title]
(. history (replaceToken token title))))

(defn get-token
"Returns the current token"
[]
(.getToken history))

(defn supported?
"Returns whether Html5History is supported"
[window]
(.isSupported Html5History window))

(defn push-state!
"Initializes push state using goog.history.Html5History
Adds an event listener to all click events and dispatches `dispatch-fn`
when the target element contains a href attribute that matches
any of the routes returned by `match-fn`
Takes in three functions:
(defn new-history
([]
(new-history (-> (TokenTransformer.) set-retrieve-token! set-create-url!)))
([transformer]
(-> (Html5History. js/window transformer) update-history!)))

(defprotocol IHistory
(set-token! [this token] [this token title])
(replace-token! [this token] [this token title])
(get-token [this])
(start! [this])
(stop! [this]))

(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"
([dispatch-fn match-fn]
(push-state! dispatch-fn match-fn identity))

(pushy dispatch-fn match-fn identity))
([dispatch-fn match-fn identity-fn]
;; We want to call `dispatch-fn` on any change to the location
(events/listen history EventType.NAVIGATE
(fn [e]
(if-let [match (-> (.-token e) match-fn identity-fn)]
(dispatch-fn match))))

;; Dispatch on initialization
(when-let [match (-> (get-token) match-fn identity-fn)]
(dispatch-fn match))
(let [history (new-history)
event-keys (atom nil)]
(reify
IHistory
(set-token! [_ token]
(. history (setToken token)))
(set-token! [_ token title]
(. history (setToken token title)))

(replace-token! [_ token]
(. history (setToken token)))
(replace-token! [_ token title]
(. history (setToken token title)))

(get-token [_]
(.getToken history))

(start! [this]
(stop! this)
;; We want to call `dispatch-fn` on any change to the location
(swap! event-keys conj
(events/listen history EventType.NAVIGATE
(fn [e]
(if-let [match (-> (.-token e) match-fn identity-fn)]
(dispatch-fn match)))))

;; Dispatch on initialization
(when-let [match (-> (get-token this) match-fn identity-fn)]
(dispatch-fn match))

(swap! event-keys conj
(on-click
(fn [e]
(when-let [el (recur-href (-> e .-target))]
(let [href (.-href el)
path (->> href (.parse Uri) .getPath)]
;; Proceed if `identity-fn` returns a value and
;; the user did not trigger the event via one of the
;; keys we should bypass
(when (and (identity-fn (match-fn path))
;; Bypass dispatch if any of these keys
(not (.-altKey e))
(not (.-ctrlKey e))
(not (.-metaKey e))
(not (.-shiftKey e))
;; Bypass if target = _blank
(not (= "_blank" (.getAttribute el "target")))
;; Bypass dispatch if middle click
(not= 1 (.-button e)))
;; Dispatch!
(if-let [title (-> el .-title)]
(set-token! this path title)
(set-token! this path))
(.preventDefault e)))))))
nil)

(stop! [this]
(doseq [key @event-keys]
(events/unlistenByKey key))
(reset! event-keys nil))))))

;; Setup event listener on all 'click' events
(on-click
(fn [e]
(when-let [el (recur-href (-> e .-target))]
(let [href (.-href el)
path (->> href (.parse Uri) .getPath)]
;; Proceed if `identity-fn` returns a value and
;; the user did not trigger the event via one of the
;; keys we should bypass
(when (and (identity-fn (match-fn path))
;; Bypass dispatch if any of these keys
(not (.-altKey e))
(not (.-ctrlKey e))
(not (.-metaKey e))
(not (.-shiftKey e))
;; Bypass if target = _blank
(not (= "_blank" (.getAttribute el "target")))
;; Bypass dispatch if middle click
(not= 1 (.-button e)))
;; Dispatch!
(set-token! path (-> el .-title))
(.preventDefault e))))))))

(defn unlisten!
"Closes the pushy event listeners"
[push-state]
(events/unlistenByKey push-state)
(events/unlisten history EventType.NAVIGATE))
(defn supported?
"Returns whether Html5History is supported"
([] (supported? js/window))
([window] (.isSupported Html5History window)))
Loading

0 comments on commit 6be68dd

Please sign in to comment.