Add examples inspired by react-router

This commit is contained in:
Valtteri Harmainen 2019-04-20 17:26:48 +03:00
parent 9241de9a43
commit 215884abe3
10 changed files with 405 additions and 0 deletions

View file

@ -0,0 +1,13 @@
# reitit-frontend example
## Usage
```clj
> lein figwheel
```
Go with browser to http://localhost:3449
## License
Copyright © 2018 Metosin Oy

View file

@ -0,0 +1,62 @@
(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.10.0"]
[ring-server "0.5.0"]
[reagent "0.8.1"]
[ring "1.7.1"]
[hiccup "1.0.5"]
[org.clojure/clojurescript "1.10.520"]
[metosin/reitit "0.3.1"]
[metosin/reitit-spec "0.3.1"]
[metosin/reitit-frontend "0.3.1"]
;; Just for pretty printting the match
[fipp "0.6.14"]]
:plugins [[lein-cljsbuild "1.1.7"]
[lein-figwheel "0.5.18"]
[cider/cider-nrepl "0.21.1"]]
:repl-options {:nrepl-middleware [cider.piggieback/wrap-cljs-repl]}
:source-paths ["src"]
:resource-paths ["resources" "target/cljsbuild"]
:profiles
{:dev
{:dependencies
[[binaryage/devtools "0.9.10"]
[cider/piggieback "0.4.0"]
[figwheel-sidecar "0.5.18"]]}}
:cljsbuild
{:builds
[{:id "app"
:figwheel true
:source-paths ["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]
:aot-cache true}}
{: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
:aot-cache true}}]}
:figwheel {:http-server-root "public"
:server-port 3449
:nrepl-port 7002
;; Server index.html for all routes for HTML5 routing
:ring-handler backend.server/handler})

View file

@ -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>

View file

@ -0,0 +1,11 @@
(ns backend.server
(:require [clojure.java.io :as io]
[ring.util.response :as resp]
[ring.middleware.content-type :as content-type]))
(def handler
(-> (fn [request]
(or (resp/resource-response (:uri request) {:root "public"})
(-> (resp/resource-response "index.html" {:root "public"})
(resp/content-type "text/html"))))
content-type/wrap-content-type))

View file

@ -0,0 +1,143 @@
(ns frontend.core
(:require [clojure.string :as string]
[fipp.edn :as fedn]
[reagent.core :as r]
[reitit.coercion :as rc]
[reitit.coercion.spec :as rss]
[reitit.frontend :as rf]
[reitit.frontend.easy :as rfe]
[spec-tools.data-spec :as ds]))
;; Components similar to react-router `Link`, `NavLink` and `Redirect`
;; with Reitit frontend.
(defn home-page []
[:div
[:h2 "Welcome to frontend"]
[:p "This is home page"]])
(defn about-page []
[:div
[:h2 "About frontend"]
[:p "This is about page"]])
(defn redirect!
"If `push` is truthy, previous page will be left in history."
[{:keys [to path-params query-params push]}]
(if push
(rfe/push-state to path-params query-params)
(rfe/replace-state to path-params query-params)))
(defn Redirect
"Component that only causes a redirect side-effect."
[props]
(redirect! props)
nil)
(defn item-page [match]
(let [{:keys [path query]} (:parameters match)
{:keys [id]} path]
(if (< id 1)
[Redirect {:to ::frontpage}]
[:div
[:h2 "Selected item " id]
(when (:foo query)
[:p "Optional foo query param: " (:foo query)])])))
(def routes
[["/"
{:name ::frontpage
:view home-page}]
["/about"
{:name ::about
:view about-page}]
["/item/:id"
{:name ::item
:view item-page
:parameters
{:path {:id int?}
:query {(ds/opt :foo) keyword?}}}]])
(def router
(rf/router routes {:data {:coercion rss/coercion}}))
(defonce current-match (r/atom nil))
(defn- resolve-href
[to path-params query-params]
(if (keyword? to)
(rfe/href to path-params query-params)
(let [match (rf/match-by-path router to)
route (-> match :data :name)
params (not-empty (or path-params (:path-params match)))
query (not-empty (or query-params (:query-params match)))]
(if match
(rfe/href route params query)
to))))
(defn Link
[{:keys [to path-params query-params active]} & children]
(let [href (resolve-href to path-params query-params)]
(into
[:a {:href href} (when active "> ")] ;; Apply styles or whatever
children)))
(defn- name-matches?
[name path-params match]
(and (= name (-> match :data :name))
(= (not-empty path-params)
(-> match :parameters :path not-empty))))
(defn- url-matches?
[url match]
(= (-> url (string/split #"\?") first)
(:path match)))
(defn NavLink
[{:keys [to path-params] :as props} & children]
(let [active (or (name-matches? to path-params @current-match)
(url-matches? to @current-match))]
[Link (assoc props :active active) children]))
(defn current-page []
[:div
[:h4 "Link"]
[:ul
[:li [Link {:to ::frontpage} "Frontpage"]]
[:li [Link {:to "/about"} "About"]]
[:li [Link {:to ::item :path-params {:id 1}} "Item 1"]]
[:li [Link {:to "/item/2?foo=bar"} "Item 2"]]
[:li [Link {:to "/item/-1"} "Item -1 (redirects to frontpage)"]]
[:li [Link {:to "http://www.google.fi"} "Google"]]]
[:h4 "NavLink"]
[:ul
[:li [NavLink {:to ::frontpage} "Frontpage"]]
[:li [NavLink {:to "/about"} "About"]]
[:li [NavLink {:to ::item :path-params {:id 1}} "Item 1"]]
[:li [NavLink {:to "/item/2?foo=bar"} "Item 2"]]
[:li [NavLink {:to "/item/-1"} "Item -1 (redirects to frontpage)"]]
[:li [NavLink {:to "http://www.google.fi"} "Google"]]]
(if @current-match
(let [view (:view (:data @current-match))]
[view @current-match]))
[:pre (with-out-str (fedn/pprint @current-match))]])
(defn init! []
(rfe/start!
router
(fn [m] (reset! current-match m))
;; set to false to enable HistoryAPI
{:use-fragment true})
(r/render [current-page] (.getElementById js/document "app")))
(init!)
(comment
(rf/match-by-path router "/about?kissa=1&koira=true")
(rf/match-by-path router "/item/2?kissa=1&koira=true"))

View file

@ -0,0 +1,13 @@
# reitit-frontend example
## Usage
```clj
> lein figwheel
```
Go with browser to http://localhost:3449
## License
Copyright © 2018 Metosin Oy

View file

@ -0,0 +1,62 @@
(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.10.0"]
[ring-server "0.5.0"]
[reagent "0.8.1"]
[ring "1.7.1"]
[hiccup "1.0.5"]
[org.clojure/clojurescript "1.10.520"]
[metosin/reitit "0.3.1"]
[metosin/reitit-spec "0.3.1"]
[metosin/reitit-frontend "0.3.1"]
;; Just for pretty printting the match
[fipp "0.6.14"]]
:plugins [[lein-cljsbuild "1.1.7"]
[lein-figwheel "0.5.18"]
[cider/cider-nrepl "0.21.1"]]
:repl-options {:nrepl-middleware [cider.piggieback/wrap-cljs-repl]}
:source-paths ["src"]
:resource-paths ["resources" "target/cljsbuild"]
:profiles
{:dev
{:dependencies
[[binaryage/devtools "0.9.10"]
[cider/piggieback "0.4.0"]
[figwheel-sidecar "0.5.18"]]}}
:cljsbuild
{:builds
[{:id "app"
:figwheel true
:source-paths ["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]
:aot-cache true}}
{: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
:aot-cache true}}]}
:figwheel {:http-server-root "public"
:server-port 3449
:nrepl-port 7002
;; Server index.html for all routes for HTML5 routing
:ring-handler backend.server/handler})

View file

@ -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>

View file

@ -0,0 +1,11 @@
(ns backend.server
(:require [clojure.java.io :as io]
[ring.util.response :as resp]
[ring.middleware.content-type :as content-type]))
(def handler
(-> (fn [request]
(or (resp/resource-response (:uri request) {:root "public"})
(-> (resp/resource-response "index.html" {:root "public"})
(resp/content-type "text/html"))))
content-type/wrap-content-type))

View file

@ -0,0 +1,70 @@
(ns frontend.core
(:require [fipp.edn :as fedn]
[reagent.core :as r]
[reitit.coercion :as rc]
[reitit.coercion.spec :as rss]
[reitit.frontend :as rf]
[reitit.frontend.easy :as rfe]
[spec-tools.data-spec :as ds]))
;; Implementing conditional prompt on navigation with Reitit frontend.
(defn home-page []
[:div
[:h2 "Home"]
[:p "You will not be prompted to leave this page"]])
(defn prompt-page []
[:div
[:h2 "Prompt"]
[:p "You will be prompted to leave this page"]])
(def routes
[["/"
{:name ::home
:view home-page}]
["/prompt"
{:name ::prompt
:view prompt-page
;; Routes can contain arbitrary keys so we add custom :prompt
;; key here. See how it's handled in `on-navigate` function.
:prompt "Are you sure you want to leave?"
;; It would be possible to define a function here that resolves
;; wheter prompting is needed or not but we'll keep it simple.
}]])
(def router
(rf/router routes {:data {:coercion rss/coercion}}))
(defonce current-match (r/atom nil))
(defn current-page []
[:div
[:ul
[:li [:a {:href (rfe/href ::home)} "Home"]]
[:li [:a {:href (rfe/href ::prompt)} "Prompt page"]]]
(if @current-match
(let [view (-> @current-match :data :view)]
[view @current-match]))
[:pre (with-out-str (fedn/pprint @current-match))]])
(defn on-navigate [m]
(if-let [prompt (and (not= @current-match m)
(-> @current-match :data :prompt))]
(if (js/window.confirm prompt) ;; Returns true if OK is pressed.
(reset! current-match m)
(.back js/window.history)) ;; Restore browser location
(reset! current-match m)))
(defn init! []
(rfe/start!
router
on-navigate
;; set to false to enable HistoryAPI
{:use-fragment true})
(r/render [current-page] (.getElementById js/document "app")))
(init!)