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]])]))