Skip to content

Commit

Permalink
Create example
Browse files Browse the repository at this point in the history
  • Loading branch information
Deraen committed Jul 12, 2018
1 parent 468a094 commit 417f35a
Show file tree
Hide file tree
Showing 12 changed files with 324 additions and 81 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ pom.xml.asc
/gh-pages
/node_modules
/_book
figwheel_server.log
1 change: 1 addition & 0 deletions examples/frontend/checkouts/reitit-core
1 change: 1 addition & 0 deletions examples/frontend/checkouts/reitit-frontend
1 change: 1 addition & 0 deletions examples/frontend/checkouts/reitit-schema
51 changes: 51 additions & 0 deletions examples/frontend/project.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
(defproject frontend "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}

:dependencies [[org.clojure/clojure "1.9.0"]
[ring-server "0.5.0"]
[reagent "0.8.1"]
[ring "1.6.3"]
[compojure "1.6.1"]
[hiccup "1.0.5"]
[org.clojure/clojurescript "1.10.238" :scope "provided"]
[metosin/reitit "0.1.3"]
[metosin/reitit-schema "0.1.3"]
[metosin/reitit-frontend "0.1.3"]]

:plugins [[lein-cljsbuild "1.1.7"]]

:source-paths []
:resource-paths ["resources" "target/cljsbuild"]

:cljsbuild
{:builds
[{:id "app"
:figwheel true
:source-paths ["src"]
:watch-paths ["src" "checkouts/reitit-frontend/src"]
:compiler {:main "frontend.core"
:asset-path "/js/out"
:output-to "target/cljsbuild/public/js/app.js"
:output-dir "target/cljsbuild/public/js/out"
:source-map true
:optimizations :none
:pretty-print true
:preloads [devtools.preload]}}
{:id "min"
:source-paths ["src"]
:compiler {:output-to "target/cljsbuild/public/js/app.js"
:output-dir "target/cljsbuild/public/js"
:source-map "target/cljsbuild/public/js/app.js.map"
:optimizations :advanced
:pretty-print false}}]}

:figwheel
{:http-server-root "public"
:server-port 3449
:nrepl-port 7002}

:profiles {:dev {:dependencies [[binaryage/devtools "0.9.10"]]
:plugins [[lein-figwheel "0.5.16"]]}})
10 changes: 10 additions & 0 deletions examples/frontend/resources/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<title>Reitit frontend example</title>
</head>
<body>
<div id="app"></div>
<script src="js/app.js"></script>
</body>
</html>
50 changes: 50 additions & 0 deletions examples/frontend/src/frontend/core.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
(ns frontend.core
(:require [reagent.core :as r]
[reitit.core :as rc]
[reitit.frontend :as rf]
[reitit.frontend.history :as rfh]
[reitit.coercion.schema :as rs]))

(def router (atom nil))

(defn home-page []
[:div
[:h2 "Welcome to frontend"]
[:div [:a {:href (rfh/href @router ::about)} "go to about page"]]])

(defn about-page []
[:div
[:h2 "About frontend"]
[:a {:href "http://google.com"} "external link"]
[:div [:a {:href (rfh/href @router ::frontpage)} "go to the home page"]]])

(defonce match (r/atom nil))

(defn current-page []
[:div
(if @match
[:div [(:view (:data @match))]])
(pr-str @match)])

(def routes
(rc/router
[""
[""
{:name ::frontpage-root
:view home-page}]
["/"
[""
{:name ::frontpage
:view home-page}]
["about"
{:name ::about
:view about-page}]]]))

(defn init! []
(reset! router (rfh/start! routes
(fn [m] (reset! match m))
{:use-fragment true
:path-prefix "/"}))
(r/render [current-page] (.getElementById js/document "app")))

(init!)
124 changes: 46 additions & 78 deletions modules/reitit-frontend/src/reitit/frontend.cljs
Original file line number Diff line number Diff line change
@@ -1,90 +1,58 @@
(ns reitit.frontend
"Utilities to implement frontend routing using Reitit.
Controller is way to declare as data the side-effects and optionally
other data related to the route."
""
(:require [reitit.core :as reitit]
[clojure.string :as str]
goog.Uri
[reitit.coercion :as coercion]))

;;
;; Utilities
;;
[reitit.coercion :as coercion]
[goog.events :as e]
[goog.dom :as dom])
(:import goog.Uri))

(defn query-params
"Parse query-params from URL into a map."
"Given goog.Uri, read query parameters into Clojure map."
[^goog.Uri uri]
(let [q (.getQueryData uri)]
(->> q
(.getKeys)
(map (juxt keyword #(.get q %)))
(into {}))))

(defn get-hash
"Given browser hash starting with #, remove the # and
end slashes."
[]
(-> js/location.hash
(subs 1)
(str/replace #"/$" "")))

;;
;; Controller implementation
;;

(defn get-params
"Get controller parameters given match. If controller provides :params
function that will be called with the match. Default is nil."
[controller match]
(if-let [f (:params controller)]
(f match)))

(defn apply-controller
"Run side-effects (:start or :stop) for controller.
The side-effect function is called with controller params."
[controller method]
(when-let [f (get controller method)]
(f (::params controller))))

(defn- pad-same-length [a b]
(concat a (take (- (count b) (count a)) (repeat nil))))

(defn apply-controllers
"Applies changes between current controllers and
those previously enabled. Resets controllers whose
parameters have changed."
[old-controllers new-match]
(let [new-controllers (map (fn [controller]
(assoc controller ::params (get-params controller new-match)))
(:controllers (:data new-match)))
changed-controllers (->> (map (fn [old new]
;; different controllers, or params changed
(if (not= old new)
{:old old, :new new}))
(pad-same-length old-controllers new-controllers)
(pad-same-length new-controllers old-controllers))
(keep identity)
vec)]
(doseq [controller (map :old changed-controllers)]
(apply-controller controller :stop))
(doseq [controller (map :new changed-controllers)]
(apply-controller controller :start))
new-controllers))

(defn hash-change [router hash]
(let [uri (goog.Uri/parse hash)
match (or (reitit/match-by-path router (.getPath uri))
{:data {:name :not-found}})
q (query-params uri)
;; Coerce if coercion enabled
c (if (:result match)
(coercion/coerce-request (:result match) {:query-params q
:path-params (:params match)})
{:query q
:path (:param match)})
;; Replace original params with coerced params
match (-> match
(assoc :query (:query c))
(assoc :params (:path c)))]
match))
(defn query-string
"Given map, creates "
[m]
(str/join "&" (map (fn [[k v]]
(str (js/encodeURIComponent (name k))
"="
;; FIXME: create protocol to handle how types are converted to string
;; FIXME: array to multiple params
(if (coll? v)
(str/join "," (map #(js/encodeURIComponent %) v))
(js/encodeURIComponent v))))
m)))

(defn match-by-path
"Given routing tree and current path, return match with possibly
coerced parameters. Return nil if no match found."
[router path]
(let [uri (.parse Uri path)]
(if-let [match (reitit/match-by-path router (.getPath uri))]
(let [q (query-params uri)
;; Return uncoerced values if coercion is not enabled - so
;; that tha parameters are always accessible from same property.
;; FIXME: coerce! can't be used as it doesn't take query-params
parameters (if (:result match)
(coercion/coerce-request (:result match) {:query-params q
:path-params (:path-params match)})
{:query q
:path (:param match)})]
(assoc match :parameters parameters)))))

(defn match-by-name
[router name params]
;; FIXME: move router not initialized to re-frame integration?
(if router
(or (reitit/match-by-name router name params)
;; FIXME: return nil?
(do
(js/console.error "Can't create URL for route " (pr-str name) (pr-str params))
nil))
::not-initialized))
114 changes: 114 additions & 0 deletions modules/reitit-frontend/src/reitit/frontend/history.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
(ns reitit.frontend.history
""
(:require [reitit.core :as reitit]
[clojure.string :as string]
[goog.events :as e]
[goog.dom :as dom]
[reitit.frontend :as rf])
(:import goog.history.Html5History
goog.Uri))

;; Token is for Closure HtmlHistory
;; Path is for reitit

(defn- token->path [history token]
(if (.-useFragment_ history)
token
(str (.getPathPrefix history) token)))

(defn- path->token [history path]
(subs path (if (.-useFragment_ history)
1
(count (.getPathPrefix history)))))

(defn- token->href [history token]
(str (if (.-useFragment_ history)
(str "#"))
(.getPathPrefix history)
token))

(def ^:private current-domain (.getDomain (.parse Uri js/location)))

(defn ignore-anchor-click
"Ignore click events from a elements, if the href points to URL that is part
of the routing tree."
[router history e]
;; Returns the next matching anchestor of event target
(when-let [el (.closest (.-target e) "a")]
(let [uri (.parse Uri (.-href el))]
(when (and (or (and (not (.hasScheme uri)) (not (.hasDomain uri)))
(= current-domain (.getDomain uri)))
(not (.-altKey e))
(not (.-ctrlKey e))
(not (.-metaKey e))
(not (.-shiftKey e))
(not (contains? #{"_blank" "self"} (.getAttribute el "target")))
;; Left button
(= 0 (.-button e))
(reitit/match-by-path router (.getPath uri)))
(.preventDefault e)
(.replaceToken history (path->token history (.getPath uri)))))))

(defn start!
"Parameters:
- router The reitit routing tree.
- on-navigate Function to be called when route changes.
Options:
- :use-fragment (default true) If true, onhashchange and location hash are used to store the token.
- :path-prefix (default \"/\") If :use-fragment is false, this is prepended to all tokens, and is
removed from start of the token before matching the route."
[router
on-navigate
{:keys [path-prefix use-fragment]
:or {path-prefix "/"
use-fragment true}}]
(let [history
(doto (Html5History.)
(.setEnabled true)
(.setPathPrefix path-prefix)
(.setUseFragment use-fragment))

event-key
(e/listen history goog.history.EventType.NAVIGATE
(fn [e]
(on-navigate (rf/match-by-path router (token->path history (.getToken history))))))

click-listen-key
(if-not use-fragment
(e/listen js/document e/EventType.CLICK
(partial ignore-anchor-click router history)))]

;; Trigger navigate event for current route
(on-navigate (rf/match-by-path router (token->path history (.getToken history))))

{:router router
:history history
:close-fn (fn []
(e/unlistenByKey event-key)
(e/unlistenByKey click-listen-key)
(.setEnabled history false))}))

(defn stop! [{:keys [close-fn]}]
(if close-fn
(close-fn)))

(defn- match->token [history match k params]
;; FIXME: query string
(if-let [path (:path match)]
(path->token history path)))

(defn href
([state k]
(href state k nil))
([state k params]
(href state k params nil))
([{:keys [router history]} k params query]
(let [match (rf/match-by-name router k params)
token (match->token history match k params)]
(token->href history token))))

(defn replace-token [{:keys [router history]} k params]
(let [match (rf/match-by-name router k params)
token (match->token history match k params)]
(.replaceToken history token)))
5 changes: 4 additions & 1 deletion project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,10 @@
[criterium "0.4.4"]
[org.clojure/test.check "0.9.0"]
[org.clojure/tools.namespace "0.2.11"]
[com.gfredericks/test.chuck "0.2.9"]]}
[com.gfredericks/test.chuck "0.2.9"]

;; https://github.com/bensu/doo/issues/180
[fipp "0.6.12"]]}
:perf {:jvm-opts ^:replace ["-server"
"-Xmx4096m"
"-Dclojure.compiler.direct-linking=true"]
Expand Down
Loading

0 comments on commit 417f35a

Please sign in to comment.