From 215884abe3c9d01072cffb64bf4f206f0a93b63c Mon Sep 17 00:00:00 2001 From: Valtteri Harmainen Date: Sat, 20 Apr 2019 17:26:48 +0300 Subject: [PATCH 1/6] Add examples inspired by react-router --- examples/frontend-links/README.md | 13 ++ examples/frontend-links/project.clj | 62 ++++++++ .../resources/public/index.html | 10 ++ .../frontend-links/src/backend/server.clj | 11 ++ .../frontend-links/src/frontend/core.cljs | 143 ++++++++++++++++++ examples/frontend-prompt/README.md | 13 ++ examples/frontend-prompt/project.clj | 62 ++++++++ .../resources/public/index.html | 10 ++ .../frontend-prompt/src/backend/server.clj | 11 ++ .../frontend-prompt/src/frontend/core.cljs | 70 +++++++++ 10 files changed, 405 insertions(+) create mode 100644 examples/frontend-links/README.md create mode 100644 examples/frontend-links/project.clj create mode 100644 examples/frontend-links/resources/public/index.html create mode 100644 examples/frontend-links/src/backend/server.clj create mode 100644 examples/frontend-links/src/frontend/core.cljs create mode 100644 examples/frontend-prompt/README.md create mode 100644 examples/frontend-prompt/project.clj create mode 100644 examples/frontend-prompt/resources/public/index.html create mode 100644 examples/frontend-prompt/src/backend/server.clj create mode 100644 examples/frontend-prompt/src/frontend/core.cljs diff --git a/examples/frontend-links/README.md b/examples/frontend-links/README.md new file mode 100644 index 00000000..72eddd36 --- /dev/null +++ b/examples/frontend-links/README.md @@ -0,0 +1,13 @@ +# reitit-frontend example + +## Usage + +```clj +> lein figwheel +``` + +Go with browser to http://localhost:3449 + +## License + +Copyright © 2018 Metosin Oy diff --git a/examples/frontend-links/project.clj b/examples/frontend-links/project.clj new file mode 100644 index 00000000..189d69fd --- /dev/null +++ b/examples/frontend-links/project.clj @@ -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}) diff --git a/examples/frontend-links/resources/public/index.html b/examples/frontend-links/resources/public/index.html new file mode 100644 index 00000000..ce1dd45b --- /dev/null +++ b/examples/frontend-links/resources/public/index.html @@ -0,0 +1,10 @@ + + + + Reitit frontend example + + +
+ + + diff --git a/examples/frontend-links/src/backend/server.clj b/examples/frontend-links/src/backend/server.clj new file mode 100644 index 00000000..88171cfa --- /dev/null +++ b/examples/frontend-links/src/backend/server.clj @@ -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)) diff --git a/examples/frontend-links/src/frontend/core.cljs b/examples/frontend-links/src/frontend/core.cljs new file mode 100644 index 00000000..eb4e5861 --- /dev/null +++ b/examples/frontend-links/src/frontend/core.cljs @@ -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")) diff --git a/examples/frontend-prompt/README.md b/examples/frontend-prompt/README.md new file mode 100644 index 00000000..72eddd36 --- /dev/null +++ b/examples/frontend-prompt/README.md @@ -0,0 +1,13 @@ +# reitit-frontend example + +## Usage + +```clj +> lein figwheel +``` + +Go with browser to http://localhost:3449 + +## License + +Copyright © 2018 Metosin Oy diff --git a/examples/frontend-prompt/project.clj b/examples/frontend-prompt/project.clj new file mode 100644 index 00000000..189d69fd --- /dev/null +++ b/examples/frontend-prompt/project.clj @@ -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}) diff --git a/examples/frontend-prompt/resources/public/index.html b/examples/frontend-prompt/resources/public/index.html new file mode 100644 index 00000000..ce1dd45b --- /dev/null +++ b/examples/frontend-prompt/resources/public/index.html @@ -0,0 +1,10 @@ + + + + Reitit frontend example + + +
+ + + diff --git a/examples/frontend-prompt/src/backend/server.clj b/examples/frontend-prompt/src/backend/server.clj new file mode 100644 index 00000000..88171cfa --- /dev/null +++ b/examples/frontend-prompt/src/backend/server.clj @@ -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)) diff --git a/examples/frontend-prompt/src/frontend/core.cljs b/examples/frontend-prompt/src/frontend/core.cljs new file mode 100644 index 00000000..27754c1d --- /dev/null +++ b/examples/frontend-prompt/src/frontend/core.cljs @@ -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!) From bcd4aa33ca0b475f9f25cf15744d25f34c1f8da1 Mon Sep 17 00:00:00 2001 From: Valtteri Harmainen Date: Sat, 20 Apr 2019 17:29:13 +0300 Subject: [PATCH 2/6] Add re-frame example --- examples/frontend-re-frame/README.md | 26 +++++++ examples/frontend-re-frame/project.clj | 52 ++++++++++++++ .../resources/public/index.html | 13 ++++ .../src/clj/frontend_re_frame/core.clj | 1 + .../src/cljs/frontend_re_frame/config.cljs | 4 ++ .../src/cljs/frontend_re_frame/core.cljs | 24 +++++++ .../src/cljs/frontend_re_frame/db.cljs | 4 ++ .../src/cljs/frontend_re_frame/events.cljs | 20 ++++++ .../src/cljs/frontend_re_frame/routes.cljs | 67 +++++++++++++++++++ .../src/cljs/frontend_re_frame/subs.cljs | 8 +++ .../src/cljs/frontend_re_frame/views.cljs | 47 +++++++++++++ 11 files changed, 266 insertions(+) create mode 100644 examples/frontend-re-frame/README.md create mode 100644 examples/frontend-re-frame/project.clj create mode 100644 examples/frontend-re-frame/resources/public/index.html create mode 100644 examples/frontend-re-frame/src/clj/frontend_re_frame/core.clj create mode 100644 examples/frontend-re-frame/src/cljs/frontend_re_frame/config.cljs create mode 100644 examples/frontend-re-frame/src/cljs/frontend_re_frame/core.cljs create mode 100644 examples/frontend-re-frame/src/cljs/frontend_re_frame/db.cljs create mode 100644 examples/frontend-re-frame/src/cljs/frontend_re_frame/events.cljs create mode 100644 examples/frontend-re-frame/src/cljs/frontend_re_frame/routes.cljs create mode 100644 examples/frontend-re-frame/src/cljs/frontend_re_frame/subs.cljs create mode 100644 examples/frontend-re-frame/src/cljs/frontend_re_frame/views.cljs diff --git a/examples/frontend-re-frame/README.md b/examples/frontend-re-frame/README.md new file mode 100644 index 00000000..f5b31f0f --- /dev/null +++ b/examples/frontend-re-frame/README.md @@ -0,0 +1,26 @@ +# frontend-re-frame + +A [re-frame](https://github.com/Day8/re-frame) application designed to ... well, that part is up to you. + +## Development Mode + +### Run application: + +``` +lein clean +lein figwheel dev +``` + +Figwheel will automatically push cljs changes to the browser. + +Wait a bit, then browse to [http://localhost:3449](http://localhost:3449). + +## Production Build + + +To compile clojurescript to javascript: + +``` +lein clean +lein cljsbuild once min +``` diff --git a/examples/frontend-re-frame/project.clj b/examples/frontend-re-frame/project.clj new file mode 100644 index 00000000..41645e53 --- /dev/null +++ b/examples/frontend-re-frame/project.clj @@ -0,0 +1,52 @@ +(defproject frontend-re-frame "0.1.0-SNAPSHOT" + :dependencies [[org.clojure/clojure "1.10.0"] + [org.clojure/clojurescript "1.10.520"] + [metosin/reitit "0.3.1"] + [reagent "0.8.1"] + [re-frame "0.10.6"]] + + :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]} + :min-lein-version "2.5.3" + :source-paths ["src/clj" "src/cljs"] + :clean-targets ^{:protect false} ["resources/public/js/compiled" "target"] + :figwheel {:css-dirs ["resources/public/css"]} + + :profiles + {:dev + {:dependencies + [[binaryage/devtools "0.9.10"] + [cider/piggieback "0.4.0"] + [figwheel-sidecar "0.5.18"]] + + :plugins [[lein-figwheel "0.5.18"]]} + :prod {}} + + :cljsbuild + {:builds + [{:id "dev" + :source-paths ["src/cljs"] + :figwheel {:on-jsload "frontend-re-frame.core/mount-root"} + :compiler {:main frontend-re-frame.core + :output-to "resources/public/js/compiled/app.js" + :output-dir "resources/public/js/compiled/out" + :asset-path "js/compiled/out" + :source-map-timestamp true + :preloads [devtools.preload] + :external-config {:devtools/config {:features-to-install :all}} + }} + + {:id "min" + :source-paths ["src/cljs"] + :compiler {:main frontend-re-frame.core + :output-to "resources/public/js/compiled/app.js" + :optimizations :advanced + :closure-defines {goog.DEBUG false} + :pretty-print false}} + + + ]} + ) diff --git a/examples/frontend-re-frame/resources/public/index.html b/examples/frontend-re-frame/resources/public/index.html new file mode 100644 index 00000000..c17ffaac --- /dev/null +++ b/examples/frontend-re-frame/resources/public/index.html @@ -0,0 +1,13 @@ + + + + + + + + +
+ + + + diff --git a/examples/frontend-re-frame/src/clj/frontend_re_frame/core.clj b/examples/frontend-re-frame/src/clj/frontend_re_frame/core.clj new file mode 100644 index 00000000..c7f9f1b7 --- /dev/null +++ b/examples/frontend-re-frame/src/clj/frontend_re_frame/core.clj @@ -0,0 +1 @@ +(ns frontend-re-frame.core) diff --git a/examples/frontend-re-frame/src/cljs/frontend_re_frame/config.cljs b/examples/frontend-re-frame/src/cljs/frontend_re_frame/config.cljs new file mode 100644 index 00000000..c017bfb3 --- /dev/null +++ b/examples/frontend-re-frame/src/cljs/frontend_re_frame/config.cljs @@ -0,0 +1,4 @@ +(ns frontend-re-frame.config) + +(def debug? + ^boolean goog.DEBUG) diff --git a/examples/frontend-re-frame/src/cljs/frontend_re_frame/core.cljs b/examples/frontend-re-frame/src/cljs/frontend_re_frame/core.cljs new file mode 100644 index 00000000..f4d8c2f0 --- /dev/null +++ b/examples/frontend-re-frame/src/cljs/frontend_re_frame/core.cljs @@ -0,0 +1,24 @@ +(ns frontend-re-frame.core + (:require + [reagent.core :as reagent] + [re-frame.core :as re-frame] + [frontend-re-frame.events :as events] + [frontend-re-frame.views :as views] + [frontend-re-frame.config :as config] + [frontend-re-frame.routes :as routes])) + +(defn dev-setup [] + (when config/debug? + (enable-console-print!) + (println "dev mode"))) + +(defn mount-root [] + (re-frame/clear-subscription-cache!) + (routes/init!) ;; Reset routes on figwheel reload + (reagent/render [views/main-panel] + (.getElementById js/document "app"))) + +(defn ^:export init [] + (re-frame/dispatch-sync [::events/initialize-db]) + (dev-setup) + (mount-root)) diff --git a/examples/frontend-re-frame/src/cljs/frontend_re_frame/db.cljs b/examples/frontend-re-frame/src/cljs/frontend_re_frame/db.cljs new file mode 100644 index 00000000..147307c0 --- /dev/null +++ b/examples/frontend-re-frame/src/cljs/frontend_re_frame/db.cljs @@ -0,0 +1,4 @@ +(ns frontend-re-frame.db) + +(def default-db + {}) diff --git a/examples/frontend-re-frame/src/cljs/frontend_re_frame/events.cljs b/examples/frontend-re-frame/src/cljs/frontend_re_frame/events.cljs new file mode 100644 index 00000000..4faaca36 --- /dev/null +++ b/examples/frontend-re-frame/src/cljs/frontend_re_frame/events.cljs @@ -0,0 +1,20 @@ +(ns frontend-re-frame.events + (:require + [re-frame.core :as re-frame] + [frontend-re-frame.db :as db])) + +(re-frame/reg-event-db + ::initialize-db + (fn [_ _] + db/default-db)) + +(re-frame/reg-event-fx + ::navigate + (fn [db [_ route]] + ;; See `navigate` effect in routes.cljs + {:frontend-re-frame.routes/navigate route})) + +(re-frame/reg-event-db + ::navigated + (fn [db [_ new-match]] + (assoc db :current-route new-match))) diff --git a/examples/frontend-re-frame/src/cljs/frontend_re_frame/routes.cljs b/examples/frontend-re-frame/src/cljs/frontend_re_frame/routes.cljs new file mode 100644 index 00000000..22fbe6bc --- /dev/null +++ b/examples/frontend-re-frame/src/cljs/frontend_re_frame/routes.cljs @@ -0,0 +1,67 @@ +(ns frontend-re-frame.routes + (:require [frontend-re-frame.events :as events] + [frontend-re-frame.subs :as subs] + [re-frame.core :as re-frame] + [reitit.coercion :as rc] + [reitit.coercion.spec :as rss] + [reitit.frontend :as rf] + [reitit.frontend.controllers :as rfc] + [reitit.frontend.easy :as rfe])) + +;; Effect for triggering navigation from events. +(re-frame/reg-fx + ::navigate + (fn [k params query] + (rfe/push-state k params query))) + +(defn href + "Return relative url for given route. Url can be used in HTML links." + ([k] + (href k nil nil)) + ([k params] + (href k params nil)) + ([k params query] + (rfe/href k params query))) + +(def routes + ["/" + ["" + {:name ::home + :human-name "Home" + :controllers + [{;; Do whatever initialization needed for home page + ;; I.e (re-frame/dispatch [::events/load-something-with-ajax]) + :start (fn [& params](js/console.log "Entering home page")) + ;; Teardown can be done here. + :stop (fn [& params] (js/console.log "Leaving home page"))}]}] + ["sub-page1" + {:name ::sub-page1 + :human-name "Sub page 1" + :controllers + [{:start (fn [& params] (js/console.log "Entering sub-page 1")) + :stop (fn [& params] (js/console.log "Leaving sub-page 1"))}]}] + ["sub-page2" + {:name ::sub-page2 + :human-name "Sub-page 2" + :controllers + [{:start (fn [& params] (js/console.log "Entering sub-page 2")) + :stop (fn [& params] (js/console.log "Leaving sub-page 2"))}]}]]) + +(def router + (rf/router + routes + {:data {:coercion rss/coercion}})) + +(defn on-navigate [new-match] + (let [old-match (re-frame/subscribe [::subs/current-route])] + (when new-match + (let [cs (rfc/apply-controllers (:controllers @old-match) new-match) + m (assoc new-match :controllers cs)] + (re-frame/dispatch [::events/navigated m]))))) + +(defn init! [] + (js/console.log "initializing routes") + (rfe/start! + router + on-navigate + {:use-fragment true})) diff --git a/examples/frontend-re-frame/src/cljs/frontend_re_frame/subs.cljs b/examples/frontend-re-frame/src/cljs/frontend_re_frame/subs.cljs new file mode 100644 index 00000000..aa210a3b --- /dev/null +++ b/examples/frontend-re-frame/src/cljs/frontend_re_frame/subs.cljs @@ -0,0 +1,8 @@ +(ns frontend-re-frame.subs + (:require + [re-frame.core :as re-frame])) + +(re-frame/reg-sub + ::current-route + (fn [db] + (:current-route db))) diff --git a/examples/frontend-re-frame/src/cljs/frontend_re_frame/views.cljs b/examples/frontend-re-frame/src/cljs/frontend_re_frame/views.cljs new file mode 100644 index 00000000..f24b143e --- /dev/null +++ b/examples/frontend-re-frame/src/cljs/frontend_re_frame/views.cljs @@ -0,0 +1,47 @@ +(ns frontend-re-frame.views + (:require + [frontend-re-frame.events :as events] + [frontend-re-frame.routes :as routes] + [frontend-re-frame.subs :as subs] + [re-frame.core :as re-frame])) + +(defn home-page [] + [:div + [:h1 "This is home page"] + [:button + ;; Dispatch navigate event that triggers a (side)effect. + {:on-click #(re-frame/dispatch [::events/navigate ::routes/sub-page2])} + "Go to sub-page 2"]]) + +(defn sub-page1 [] + [:div + [:h1 "This is sub-page 1"]]) + +(defn sub-page2 [] + [:div + [:h1 "This is sub-page 2"]]) + +(defn nav [] + (let [current-route (re-frame/subscribe [::subs/current-route])] + [:div + (into + [:ul] + (for [route (->> routes/routes (filter vector?) (map second)) + :let [text (-> route :human-name)]] + [:li + (when (= (:name route) (-> @current-route :data :name)) + "> ") + ;; Create a normal links that user can click + [:a {:href (routes/href (:name route))} text]]))])) + +(defn main-panel [] + (let [current-route (re-frame/subscribe [::subs/current-route])] + [:div + [nav] + (condp = (-> @current-route :data :name) + ::routes/home [home-page] + ::routes/sub-page1 [sub-page1] + ::routes/sub-page2 [sub-page2] + [:div + [:p (str "Unknown page")] + [:pre @current-route]])])) From 66c6363365f1a8e7fb51bfb925cbb5949a0ae498 Mon Sep 17 00:00:00 2001 From: Valtteri Harmainen Date: Sat, 20 Apr 2019 19:58:59 +0300 Subject: [PATCH 3/6] Add backend handler to re-frame example To enable running example with :use-fragment false --- examples/frontend-re-frame/project.clj | 6 +++++- examples/frontend-re-frame/src/clj/backend/server.clj | 11 +++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 examples/frontend-re-frame/src/clj/backend/server.clj diff --git a/examples/frontend-re-frame/project.clj b/examples/frontend-re-frame/project.clj index 41645e53..b487ce7c 100644 --- a/examples/frontend-re-frame/project.clj +++ b/examples/frontend-re-frame/project.clj @@ -13,7 +13,11 @@ :min-lein-version "2.5.3" :source-paths ["src/clj" "src/cljs"] :clean-targets ^{:protect false} ["resources/public/js/compiled" "target"] - :figwheel {:css-dirs ["resources/public/css"]} + :figwheel + {:css-dirs ["resources/public/css"] + :server-port 3449 + :nrepl-port 7002 + :ring-handler backend.server/handler} :profiles {:dev diff --git a/examples/frontend-re-frame/src/clj/backend/server.clj b/examples/frontend-re-frame/src/clj/backend/server.clj new file mode 100644 index 00000000..88171cfa --- /dev/null +++ b/examples/frontend-re-frame/src/clj/backend/server.clj @@ -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)) From 22f38d99924348b54a9746e5d2bc3be02378f5e1 Mon Sep 17 00:00:00 2001 From: Valtteri Harmainen Date: Thu, 25 Apr 2019 10:23:55 +0300 Subject: [PATCH 4/6] Revamped re-frame example - Flattened into single file - Changed router to decide which view to render --- .../src/cljs/frontend_re_frame/config.cljs | 4 - .../src/cljs/frontend_re_frame/core.cljs | 151 ++++++++++++++++-- .../src/cljs/frontend_re_frame/db.cljs | 4 - .../src/cljs/frontend_re_frame/events.cljs | 20 --- .../src/cljs/frontend_re_frame/routes.cljs | 67 -------- .../src/cljs/frontend_re_frame/subs.cljs | 8 - .../src/cljs/frontend_re_frame/views.cljs | 47 ------ 7 files changed, 142 insertions(+), 159 deletions(-) delete mode 100644 examples/frontend-re-frame/src/cljs/frontend_re_frame/config.cljs delete mode 100644 examples/frontend-re-frame/src/cljs/frontend_re_frame/db.cljs delete mode 100644 examples/frontend-re-frame/src/cljs/frontend_re_frame/events.cljs delete mode 100644 examples/frontend-re-frame/src/cljs/frontend_re_frame/routes.cljs delete mode 100644 examples/frontend-re-frame/src/cljs/frontend_re_frame/subs.cljs delete mode 100644 examples/frontend-re-frame/src/cljs/frontend_re_frame/views.cljs diff --git a/examples/frontend-re-frame/src/cljs/frontend_re_frame/config.cljs b/examples/frontend-re-frame/src/cljs/frontend_re_frame/config.cljs deleted file mode 100644 index c017bfb3..00000000 --- a/examples/frontend-re-frame/src/cljs/frontend_re_frame/config.cljs +++ /dev/null @@ -1,4 +0,0 @@ -(ns frontend-re-frame.config) - -(def debug? - ^boolean goog.DEBUG) diff --git a/examples/frontend-re-frame/src/cljs/frontend_re_frame/core.cljs b/examples/frontend-re-frame/src/cljs/frontend_re_frame/core.cljs index f4d8c2f0..7999ba5f 100644 --- a/examples/frontend-re-frame/src/cljs/frontend_re_frame/core.cljs +++ b/examples/frontend-re-frame/src/cljs/frontend_re_frame/core.cljs @@ -1,24 +1,157 @@ (ns frontend-re-frame.core (:require - [reagent.core :as reagent] [re-frame.core :as re-frame] - [frontend-re-frame.events :as events] - [frontend-re-frame.views :as views] - [frontend-re-frame.config :as config] - [frontend-re-frame.routes :as routes])) + [reagent.core :as reagent] + [reitit.core :as r] + [reitit.coercion :as rc] + [reitit.coercion.spec :as rss] + [reitit.frontend :as rf] + [reitit.frontend.controllers :as rfc] + [reitit.frontend.easy :as rfe])) + +;;; Events ;;; + +(re-frame/reg-event-db + ::initialize-db + (fn [_ _] + {:current-route nil})) + +(re-frame/reg-event-fx + ::navigate + (fn [db [_ route]] + ;; See `navigate` effect in routes.cljs + {::navigate! route})) + +(re-frame/reg-event-db + ::navigated + (fn [db [_ new-match]] + (assoc db :current-route new-match))) + +;;; Subscriptions ;;; + +(re-frame/reg-sub + ::current-route + (fn [db] + (:current-route db))) + +;;; Views ;;; + +(defn home-page [] + [:div + [:h1 "This is home page"] + [:button + ;; Dispatch navigate event that triggers a (side)effect. + {:on-click #(re-frame/dispatch [::navigate ::sub-page2])} + "Go to sub-page 2"]]) + +(defn sub-page1 [] + [:div + [:h1 "This is sub-page 1"]]) + +(defn sub-page2 [] + [:div + [:h1 "This is sub-page 2"]]) + +;;; Effects ;;; + +;; Triggering navigation from events. +(re-frame/reg-fx + ::navigate! + (fn [k params query] + (rfe/push-state k params query))) + +;;; Routes ;;; + +(defn href + "Return relative url for given route. Url can be used in HTML links." + ([k] + (href k nil nil)) + ([k params] + (href k params nil)) + ([k params query] + (rfe/href k params query))) + +(def routes + ["/" + ["" + {:name ::home + :view home-page + :link-text "Home" + :controllers + [{;; Do whatever initialization needed for home page + ;; I.e (re-frame/dispatch [::events/load-something-with-ajax]) + :start (fn [& params](js/console.log "Entering home page")) + ;; Teardown can be done here. + :stop (fn [& params] (js/console.log "Leaving home page"))}]}] + ["sub-page1" + {:name ::sub-page1 + :view sub-page1 + :link-text "Sub page 1" + :controllers + [{:start (fn [& params] (js/console.log "Entering sub-page 1")) + :stop (fn [& params] (js/console.log "Leaving sub-page 1"))}]}] + ["sub-page2" + {:name ::sub-page2 + :view sub-page2 + :link-text "Sub-page 2" + :controllers + [{:start (fn [& params] (js/console.log "Entering sub-page 2")) + :stop (fn [& params] (js/console.log "Leaving sub-page 2"))}]}]]) + +(defn on-navigate [new-match] + (let [old-match (re-frame/subscribe [::current-route])] + (when new-match + (let [cs (rfc/apply-controllers (:controllers @old-match) new-match) + m (assoc new-match :controllers cs)] + (re-frame/dispatch [::navigated m]))))) + +(def router + (rf/router + routes + {:data {:coercion rss/coercion}})) + +(defn init-routes! [] + (js/console.log "initializing routes") + (rfe/start! + router + on-navigate + {:use-fragment true})) + +(defn nav [{:keys [router current-route]}] + (into + [:ul] + (for [route-name (r/route-names router) + :let [route (r/match-by-name router route-name) + text (-> route :data :link-text)]] + [:li + (when (= route-name (-> current-route :data :name)) + "> ") + ;; Create a normal links that user can click + [:a {:href (href route-name)} text]]))) + +(defn router-component [{:keys [router]}] + (let [current-route @(re-frame/subscribe [::current-route])] + [:div + [nav {:router router :current-route current-route}] + (when current-route + [(-> current-route :data :view)])])) + +;;; Setup ;;; + +(def debug? ^boolean goog.DEBUG) (defn dev-setup [] - (when config/debug? + (when debug? (enable-console-print!) (println "dev mode"))) (defn mount-root [] (re-frame/clear-subscription-cache!) - (routes/init!) ;; Reset routes on figwheel reload - (reagent/render [views/main-panel] + (init-routes!) ;; Reset routes on figwheel reload + (reagent/render [router-component {:router router}] (.getElementById js/document "app"))) (defn ^:export init [] - (re-frame/dispatch-sync [::events/initialize-db]) + (re-frame/dispatch-sync [::initialize-db]) (dev-setup) (mount-root)) diff --git a/examples/frontend-re-frame/src/cljs/frontend_re_frame/db.cljs b/examples/frontend-re-frame/src/cljs/frontend_re_frame/db.cljs deleted file mode 100644 index 147307c0..00000000 --- a/examples/frontend-re-frame/src/cljs/frontend_re_frame/db.cljs +++ /dev/null @@ -1,4 +0,0 @@ -(ns frontend-re-frame.db) - -(def default-db - {}) diff --git a/examples/frontend-re-frame/src/cljs/frontend_re_frame/events.cljs b/examples/frontend-re-frame/src/cljs/frontend_re_frame/events.cljs deleted file mode 100644 index 4faaca36..00000000 --- a/examples/frontend-re-frame/src/cljs/frontend_re_frame/events.cljs +++ /dev/null @@ -1,20 +0,0 @@ -(ns frontend-re-frame.events - (:require - [re-frame.core :as re-frame] - [frontend-re-frame.db :as db])) - -(re-frame/reg-event-db - ::initialize-db - (fn [_ _] - db/default-db)) - -(re-frame/reg-event-fx - ::navigate - (fn [db [_ route]] - ;; See `navigate` effect in routes.cljs - {:frontend-re-frame.routes/navigate route})) - -(re-frame/reg-event-db - ::navigated - (fn [db [_ new-match]] - (assoc db :current-route new-match))) diff --git a/examples/frontend-re-frame/src/cljs/frontend_re_frame/routes.cljs b/examples/frontend-re-frame/src/cljs/frontend_re_frame/routes.cljs deleted file mode 100644 index 22fbe6bc..00000000 --- a/examples/frontend-re-frame/src/cljs/frontend_re_frame/routes.cljs +++ /dev/null @@ -1,67 +0,0 @@ -(ns frontend-re-frame.routes - (:require [frontend-re-frame.events :as events] - [frontend-re-frame.subs :as subs] - [re-frame.core :as re-frame] - [reitit.coercion :as rc] - [reitit.coercion.spec :as rss] - [reitit.frontend :as rf] - [reitit.frontend.controllers :as rfc] - [reitit.frontend.easy :as rfe])) - -;; Effect for triggering navigation from events. -(re-frame/reg-fx - ::navigate - (fn [k params query] - (rfe/push-state k params query))) - -(defn href - "Return relative url for given route. Url can be used in HTML links." - ([k] - (href k nil nil)) - ([k params] - (href k params nil)) - ([k params query] - (rfe/href k params query))) - -(def routes - ["/" - ["" - {:name ::home - :human-name "Home" - :controllers - [{;; Do whatever initialization needed for home page - ;; I.e (re-frame/dispatch [::events/load-something-with-ajax]) - :start (fn [& params](js/console.log "Entering home page")) - ;; Teardown can be done here. - :stop (fn [& params] (js/console.log "Leaving home page"))}]}] - ["sub-page1" - {:name ::sub-page1 - :human-name "Sub page 1" - :controllers - [{:start (fn [& params] (js/console.log "Entering sub-page 1")) - :stop (fn [& params] (js/console.log "Leaving sub-page 1"))}]}] - ["sub-page2" - {:name ::sub-page2 - :human-name "Sub-page 2" - :controllers - [{:start (fn [& params] (js/console.log "Entering sub-page 2")) - :stop (fn [& params] (js/console.log "Leaving sub-page 2"))}]}]]) - -(def router - (rf/router - routes - {:data {:coercion rss/coercion}})) - -(defn on-navigate [new-match] - (let [old-match (re-frame/subscribe [::subs/current-route])] - (when new-match - (let [cs (rfc/apply-controllers (:controllers @old-match) new-match) - m (assoc new-match :controllers cs)] - (re-frame/dispatch [::events/navigated m]))))) - -(defn init! [] - (js/console.log "initializing routes") - (rfe/start! - router - on-navigate - {:use-fragment true})) diff --git a/examples/frontend-re-frame/src/cljs/frontend_re_frame/subs.cljs b/examples/frontend-re-frame/src/cljs/frontend_re_frame/subs.cljs deleted file mode 100644 index aa210a3b..00000000 --- a/examples/frontend-re-frame/src/cljs/frontend_re_frame/subs.cljs +++ /dev/null @@ -1,8 +0,0 @@ -(ns frontend-re-frame.subs - (:require - [re-frame.core :as re-frame])) - -(re-frame/reg-sub - ::current-route - (fn [db] - (:current-route db))) diff --git a/examples/frontend-re-frame/src/cljs/frontend_re_frame/views.cljs b/examples/frontend-re-frame/src/cljs/frontend_re_frame/views.cljs deleted file mode 100644 index f24b143e..00000000 --- a/examples/frontend-re-frame/src/cljs/frontend_re_frame/views.cljs +++ /dev/null @@ -1,47 +0,0 @@ -(ns frontend-re-frame.views - (:require - [frontend-re-frame.events :as events] - [frontend-re-frame.routes :as routes] - [frontend-re-frame.subs :as subs] - [re-frame.core :as re-frame])) - -(defn home-page [] - [:div - [:h1 "This is home page"] - [:button - ;; Dispatch navigate event that triggers a (side)effect. - {:on-click #(re-frame/dispatch [::events/navigate ::routes/sub-page2])} - "Go to sub-page 2"]]) - -(defn sub-page1 [] - [:div - [:h1 "This is sub-page 1"]]) - -(defn sub-page2 [] - [:div - [:h1 "This is sub-page 2"]]) - -(defn nav [] - (let [current-route (re-frame/subscribe [::subs/current-route])] - [:div - (into - [:ul] - (for [route (->> routes/routes (filter vector?) (map second)) - :let [text (-> route :human-name)]] - [:li - (when (= (:name route) (-> @current-route :data :name)) - "> ") - ;; Create a normal links that user can click - [:a {:href (routes/href (:name route))} text]]))])) - -(defn main-panel [] - (let [current-route (re-frame/subscribe [::subs/current-route])] - [:div - [nav] - (condp = (-> @current-route :data :name) - ::routes/home [home-page] - ::routes/sub-page1 [sub-page1] - ::routes/sub-page2 [sub-page2] - [:div - [:p (str "Unknown page")] - [:pre @current-route]])])) From 0919b462360fbdbae4bb29270377981bb6ec7912 Mon Sep 17 00:00:00 2001 From: Valtteri Harmainen Date: Fri, 26 Apr 2019 15:52:44 +0300 Subject: [PATCH 5/6] Fixes based on review --- examples/frontend-links/src/frontend/core.cljs | 8 ++++++-- .../src/cljs/frontend_re_frame/core.cljs | 18 ++++++++---------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/examples/frontend-links/src/frontend/core.cljs b/examples/frontend-links/src/frontend/core.cljs index eb4e5861..03e82520 100644 --- a/examples/frontend-links/src/frontend/core.cljs +++ b/examples/frontend-links/src/frontend/core.cljs @@ -31,8 +31,12 @@ (defn Redirect "Component that only causes a redirect side-effect." [props] - (redirect! props) - nil) + (r/create-class + {:component-did-mount (fn [this] (redirect! (r/props this))) + :component-did-update (fn [this [_ prev-props]] + (if (not= (r/props this) prev-props) + (redirect! (r/props this)))) + :render (fn [this] nil)})) (defn item-page [match] (let [{:keys [path query]} (:parameters match) diff --git a/examples/frontend-re-frame/src/cljs/frontend_re_frame/core.cljs b/examples/frontend-re-frame/src/cljs/frontend_re_frame/core.cljs index 7999ba5f..f7b33cc8 100644 --- a/examples/frontend-re-frame/src/cljs/frontend_re_frame/core.cljs +++ b/examples/frontend-re-frame/src/cljs/frontend_re_frame/core.cljs @@ -25,7 +25,9 @@ (re-frame/reg-event-db ::navigated (fn [db [_ new-match]] - (assoc db :current-route new-match))) + (let [old-match (:current-route db) + controllers (rfc/apply-controllers (:controllers old-match) new-match)] + (assoc db :current-route (assoc new-match :controllers controllers))))) ;;; Subscriptions ;;; @@ -99,11 +101,8 @@ :stop (fn [& params] (js/console.log "Leaving sub-page 2"))}]}]]) (defn on-navigate [new-match] - (let [old-match (re-frame/subscribe [::current-route])] - (when new-match - (let [cs (rfc/apply-controllers (:controllers @old-match) new-match) - m (assoc new-match :controllers cs)] - (re-frame/dispatch [::navigated m]))))) + (when new-match + (re-frame/dispatch [::navigated new-match]))) (def router (rf/router @@ -118,16 +117,15 @@ {:use-fragment true})) (defn nav [{:keys [router current-route]}] - (into - [:ul] + [:ul (for [route-name (r/route-names router) :let [route (r/match-by-name router route-name) text (-> route :data :link-text)]] - [:li + [:li {:key route-name} (when (= route-name (-> current-route :data :name)) "> ") ;; Create a normal links that user can click - [:a {:href (href route-name)} text]]))) + [:a {:href (href route-name)} text]])]) (defn router-component [{:keys [router]}] (let [current-route @(re-frame/subscribe [::current-route])] From f7332d3bc4bc8f087d5866240ca2cbd3415308ce Mon Sep 17 00:00:00 2001 From: Valtteri Harmainen Date: Fri, 26 Apr 2019 16:39:11 +0300 Subject: [PATCH 6/6] Fix empty query params map resulting in redundant '?' in path --- examples/frontend-links/src/frontend/core.cljs | 4 ++-- modules/reitit-core/src/reitit/core.cljc | 2 +- test/cljc/reitit/core_test.cljc | 4 ++++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/examples/frontend-links/src/frontend/core.cljs b/examples/frontend-links/src/frontend/core.cljs index 03e82520..cc84dcf3 100644 --- a/examples/frontend-links/src/frontend/core.cljs +++ b/examples/frontend-links/src/frontend/core.cljs @@ -75,8 +75,8 @@ (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)))] + params (or path-params (:path-params match)) + query (or query-params (:query-params match))] (if match (rfe/href route params query) to)))) diff --git a/modules/reitit-core/src/reitit/core.cljc b/modules/reitit-core/src/reitit/core.cljc index 2f33c605..573ba5b2 100644 --- a/modules/reitit-core/src/reitit/core.cljc +++ b/modules/reitit-core/src/reitit/core.cljc @@ -67,7 +67,7 @@ ([match] (match->path match nil)) ([match query-params] - (some-> match :path (cond-> query-params (str "?" (impl/query-string query-params)))))) + (some-> match :path (cond-> (seq query-params) (str "?" (impl/query-string query-params)))))) ;; ;; Different routers diff --git a/test/cljc/reitit/core_test.cljc b/test/cljc/reitit/core_test.cljc index c48f6348..4d9ee770 100644 --- a/test/cljc/reitit/core_test.cljc +++ b/test/cljc/reitit/core_test.cljc @@ -354,6 +354,10 @@ (-> router (r/match-by-name! ::route {:a "olipa", :b "kerran"}) (r/match->path)))) + (is (= "/olipa/kerran" + (-> router + (r/match-by-name! ::route {:a "olipa", :b "kerran"}) + (r/match->path {})))) (is (= "/olipa/kerran?iso=p%C3%B6ril%C3%A4inen" (-> router (r/match-by-name! ::route {:a "olipa", :b "kerran"})