Table of content.
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
This commit is contained in:
parent
7c90ecf584
commit
683efa1845
|
@ -1,7 +1,4 @@
|
||||||
#+TITLE: Minimal clojurescript reagent reitit example
|
#+TITLE: Minimal clojurescript reagent example
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
* Components
|
* Components
|
||||||
Reagent allow multiple ways to create components with increasing complexity.
|
Reagent allow multiple ways to create components with increasing complexity.
|
||||||
|
@ -16,7 +13,7 @@ In the example below the function just returns some hiccup with the parameters i
|
||||||
(defn navbar-link [{:keys [href title text] :or {text nil title nil}}]
|
(defn navbar-link [{:keys [href title text] :or {text nil title nil}}]
|
||||||
[:a.link.dim.dib.mr3 {:key href :href href :title title} text])
|
[:a.link.dim.dib.mr3 {:key href :href href :title title} text])
|
||||||
|
|
||||||
(navbar-link {:href "https://clojure.org" :title "linke title" :text "link here"})
|
[navbar-link {:href "https://clojure.org" :title "linke title" :text "link here"}]
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
|
|
||||||
|
@ -31,21 +28,26 @@ In the example below the function just returns some hiccup with the parameters i
|
||||||
[:div.dtc.tr [:h2.f5.mv0 amount]]]
|
[:div.dtc.tr [:h2.f5.mv0 amount]]]
|
||||||
[:p.f6.lh-copy.measure.mt2.mid-gray description]]])
|
[:p.f6.lh-copy.measure.mt2.mid-gray description]]])
|
||||||
|
|
||||||
(product-card
|
[:div.flex
|
||||||
|
[product-card
|
||||||
{:title "Cat 01"
|
{:title "Cat 01"
|
||||||
:amount "£54.59"
|
:amount "£54.59"
|
||||||
:description "Cat description here"
|
:description "Cat 1 description here"
|
||||||
:link "http://placekitten.com/g/600/300"})
|
:link "http://placekitten.com/g/600/300"}]
|
||||||
|
[product-card
|
||||||
|
{:title "Cat 02"
|
||||||
|
:amount "£34.59"
|
||||||
|
:description "Cat 2 description here"
|
||||||
|
:link "http://placekitten.com/g/600/300"}]]
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
|
|
||||||
** Form 2 components
|
** Form 2 components
|
||||||
In form two we can track local state inside a component a click counter being a basic example.
|
In form two we can track local state inside a component a click counter being a basic example.
|
||||||
|
|
||||||
|
|
||||||
#+BEGIN_SRC clojure
|
#+BEGIN_SRC clojure
|
||||||
(defn my-component [title value]
|
(defn my-component [title starting-value]
|
||||||
(let [local-state (reagent/atom value)]
|
(let [local-state (reagent/atom starting-value)]
|
||||||
(fn [title]
|
(fn [title]
|
||||||
[:h1 {:class (when @local-state "hide")
|
[:h1 {:class (when @local-state "hide")
|
||||||
:on-click (fn [] (swap! local-state inc))}
|
:on-click (fn [] (swap! local-state inc))}
|
||||||
|
@ -54,13 +56,11 @@ In form two we can track local state inside a component a click counter being a
|
||||||
[my-component "Clickable component" 1]
|
[my-component "Clickable component" 1]
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
|
|
||||||
** Form 3 components
|
** Form 3 components
|
||||||
This form of component give's you full access to the react life cycle methods, so render did-mount did-unmount etc
|
This form of component give's you full access to the react life cycle methods, so render did-mount did-unmount etc
|
||||||
usually this form of component is only needed when rendering graphics or things like graphs, it's also useful for capturing errors and handling them as in the example below, which renders your components but if =component-did-catch= is trigger the error is caught and displayed instead.
|
usually this form of component is only needed when rendering graphics or things like graphs, it's also useful for capturing errors and handling them as in the example below, which renders your components but if =component-did-catch= is trigger the error is caught and displayed instead.
|
||||||
|
|
||||||
#+BEGIN_SRC clojurescript
|
#+BEGIN_SRC clojurescript
|
||||||
;;(require '[reagent.core :as reagent])
|
|
||||||
;;(require '[reitit.frontend.easy :as rfe])
|
|
||||||
(defn err-boundary
|
(defn err-boundary
|
||||||
[& children]
|
[& children]
|
||||||
(let [err-state (reagent/atom nil)]
|
(let [err-state (reagent/atom nil)]
|
||||||
|
@ -75,47 +75,18 @@ usually this form of component is only needed when rendering graphics or things
|
||||||
[:pre [:code (pr-str info)]])))})))
|
[:pre [:code (pr-str info)]])))})))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
|
* Fetching a html element reference
|
||||||
|
If we wish to capture a node we can use =:ref= and store the result in an atom, we can then de reference the atom and call a method on the node using =aget=.
|
||||||
|
|
||||||
* Routing
|
#+BEGIN_SRC clojurescript :results output
|
||||||
Routing with reitit is all about data, you store your routes as nested vectors of hash maps.
|
(defn example-ref-component [title]
|
||||||
the hash map should take a name and view param at least but you can add in params and validate the data.
|
(let [local-ref (reagent/atom nil)]
|
||||||
|
(fn [title]
|
||||||
|
[:div#example-ref-id.flex.items-center.justify-center.pa4.bg-lightest-blue.navy
|
||||||
|
{:ref #(reset! local-ref %)}
|
||||||
|
(str title (when @local-ref (aget @local-ref "id")))])))
|
||||||
|
|
||||||
Reitit works as a backend and frontend routing library so you can share routes between the two.
|
[example-ref-component "Grabbing the element id using ref "]
|
||||||
|
|
||||||
These are a few simple routes, the last takes parameters and does validation checking against the values.
|
|
||||||
#+BEGIN_SRC clojurescript
|
|
||||||
(def routes
|
|
||||||
[["/"
|
|
||||||
{:name ::frontpage
|
|
||||||
:view 'home-page-function}]
|
|
||||||
|
|
||||||
["/about"
|
|
||||||
{:name ::about
|
|
||||||
:view 'about-page-function}]
|
|
||||||
|
|
||||||
["/item/:id"
|
|
||||||
{:name ::item
|
|
||||||
:view 'item-page-function
|
|
||||||
:parameters {:path {:id int?}
|
|
||||||
:query {:foo keyword?}}}]])
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
You need to connect your routes data structure to =ref/start!= this function take's your own function where you can handle what should happen on route change, in this example an atom is updated causing react to render the new page.
|
|
||||||
|
|
||||||
#+BEGIN_SRC clojurescript
|
|
||||||
(def site-state (reagent/atom nil))
|
|
||||||
|
|
||||||
(rfe/start!
|
|
||||||
(rf/router routes {:data {:coercion rss/coercion}})
|
|
||||||
(fn [m] (swap! site-state assoc :current-route m))
|
|
||||||
;; set to false to enable HistoryAPI
|
|
||||||
{:use-fragment true})
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
|
|
||||||
To create a link to a route, you can use the =rfe/href= function which takes a lookup key which you specified in your routes, in this instance the key is name spaced to the current namespace.
|
|
||||||
#+BEGIN_SRC clojurescript
|
|
||||||
[:a {:href (rfe/href ::frontpage)} "example link"]
|
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
<link rel="stylesheet" type="text/css" href="https://unpkg.com/tachyons@4.12.0/css/tachyons.min.css">
|
<link rel="stylesheet" type="text/css" href="https://unpkg.com/tachyons@4.12.0/css/tachyons.min.css">
|
||||||
<link rel="stylesheet" type="text/css" href="https://storage.googleapis.com/app.klipse.tech/css/codemirror.css">
|
<link rel="stylesheet" type="text/css" href="https://storage.googleapis.com/app.klipse.tech/css/codemirror.css">
|
||||||
<link rel="stylesheet" type="text/css" href="https://raw.githubusercontent.com/FarhadG/code-mirror-themes/master/themes/rdark.css">
|
<link rel="stylesheet" type="text/css" href="https://raw.githubusercontent.com/FarhadG/code-mirror-themes/master/themes/rdark.css">
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
.cm-s-rdark {
|
.cm-s-rdark {
|
||||||
|
|
|
@ -1,30 +1,29 @@
|
||||||
(ns ^:figwheel-hooks clojure-demo.core
|
(ns ^:figwheel-hooks clojure-demo.core
|
||||||
(:require ["react-dom/client" :refer [createRoot]]
|
(:require
|
||||||
["@codemirror/state" :as cm-state :refer [EditorState Transaction] ]
|
|
||||||
["@codemirror/language" :refer [LanguageSupport StreamLanguage]]
|
["@codemirror/language" :refer [LanguageSupport StreamLanguage]]
|
||||||
["@codemirror/view" :as cm-view :refer [EditorView ViewUpdate]]
|
|
||||||
["@codemirror/commands" :as cm-commands :refer [defaultKeymap]]
|
|
||||||
["@codemirror/theme-one-dark" :refer [oneDark]]
|
|
||||||
["@nextjournal/lang-clojure" :refer [clojure]]
|
|
||||||
["@codemirror/legacy-modes/mode/yaml" :refer [yaml]]
|
["@codemirror/legacy-modes/mode/yaml" :refer [yaml]]
|
||||||
[reagent.core :as reagent]
|
["@codemirror/state" :as cm-state :refer [EditorState Transaction]]
|
||||||
|
["@codemirror/theme-one-dark" :refer [oneDark]]
|
||||||
|
["@codemirror/view" :as cm-view :refer [EditorView ViewUpdate]]
|
||||||
|
["@nextjournal/lang-clojure" :refer [clojure]]
|
||||||
|
["react-dom/client" :refer [createRoot]]
|
||||||
[ajax.core :refer [GET raw-response-format]]
|
[ajax.core :refer [GET raw-response-format]]
|
||||||
[clojure.set :refer [rename-keys]]
|
[cl-eorg.html :as h :refer [body headers org->replacements]]
|
||||||
[cl-eorg.parser :as o :refer [parse parse-flat]]
|
[cl-eorg.parser :as o :refer [parse]]
|
||||||
[cl-eorg.html :refer [org->replacements]]
|
|
||||||
[cl-eorg.themes.tachyon :refer [tachyon-theme]]
|
[cl-eorg.themes.tachyon :refer [tachyon-theme]]
|
||||||
[reitit.frontend :as rf]
|
[clojure.set :refer [rename-keys]]
|
||||||
[reitit.frontend.easy :as rfe]
|
|
||||||
[reitit.coercion.spec :as rss]
|
|
||||||
;; interactive
|
|
||||||
[sci.core :as sci]
|
|
||||||
[sci.configs.reagent.reagent :as sci-reagent]
|
|
||||||
[sci.configs.tonsky.datascript :as sci-datascript]
|
|
||||||
[honey.sql :as sql]
|
[honey.sql :as sql]
|
||||||
[honey.sql.helpers :as sqlh]
|
[honey.sql.helpers :as sqlh]
|
||||||
|
[reagent.core :as reagent]
|
||||||
|
[reitit.coercion.spec :as rss]
|
||||||
|
[reitit.frontend :as rf]
|
||||||
|
[reitit.frontend.easy :as rfe]
|
||||||
|
[reitit.frontend.history :refer [ignore-anchor-click?]]
|
||||||
|
[sci.configs.reagent.reagent :as sci-reagent]
|
||||||
|
[sci.configs.tonsky.datascript :as sci-datascript]
|
||||||
|
[sci.core :as sci]
|
||||||
[spec-tools.data-spec :as ds]))
|
[spec-tools.data-spec :as ds]))
|
||||||
|
|
||||||
|
|
||||||
(def languages {"clojure" {:mode "clojure"}
|
(def languages {"clojure" {:mode "clojure"}
|
||||||
"clojurescript" {:mode "clojure"}
|
"clojurescript" {:mode "clojure"}
|
||||||
"yaml" {:mode "yaml"}})
|
"yaml" {:mode "yaml"}})
|
||||||
|
@ -60,6 +59,13 @@
|
||||||
;(def sci-ctx (sci/empty-environment))
|
;(def sci-ctx (sci/empty-environment))
|
||||||
(sci/alter-var-root sci/print-fn (constantly *print-fn*))
|
(sci/alter-var-root sci/print-fn (constantly *print-fn*))
|
||||||
|
|
||||||
|
(defn slugify [s]
|
||||||
|
(str "toc"
|
||||||
|
(-> s
|
||||||
|
(clojure.string/lower-case)
|
||||||
|
(clojure.string/replace #"[^\w]+" "-")
|
||||||
|
(clojure.string/replace #"^-\\|-\\-$" ""))))
|
||||||
|
|
||||||
(defn fetch-selected-text
|
(defn fetch-selected-text
|
||||||
"Get the users selected text"
|
"Get the users selected text"
|
||||||
[updated-view transactions]
|
[updated-view transactions]
|
||||||
|
@ -104,9 +110,6 @@
|
||||||
(reagent/create-class
|
(reagent/create-class
|
||||||
{:component-did-mount
|
{:component-did-mount
|
||||||
(fn [_]
|
(fn [_]
|
||||||
;(prn "mounted")
|
|
||||||
;(prn @editor)
|
|
||||||
;(prn (.current @editor))
|
|
||||||
(reset! view (EditorView.
|
(reset! view (EditorView.
|
||||||
(clj->js (merge {} #_(get languages language)
|
(clj->js (merge {} #_(get languages language)
|
||||||
{:state start-state
|
{:state start-state
|
||||||
|
@ -147,14 +150,21 @@
|
||||||
{:href (:href (second v))}
|
{:href (:href (second v))}
|
||||||
(:title (second v))))))
|
(:title (second v))))))
|
||||||
|
|
||||||
|
|
||||||
(def theme
|
(def theme
|
||||||
(merge tachyon-theme
|
(merge tachyon-theme
|
||||||
{;:SRC code-editor
|
{:SRC code-editor
|
||||||
:HEADER1 (fn [v _] [:h1.f3.fw6.f3-ns.lh-title.mt0.mb2 {:title "title-here"} v])
|
:HEADER1 (fn [v _] [:h1.f2.fw6.f3-ns.lh-title.mt0.mb2 {:id (slugify v)} [:a {:name (slugify v)} v]])
|
||||||
:HEADER2 (fn [v _] [:h1.f3.fw6.f3-ns.lh-title.mt0.mb2 {:title "title-here"} v])
|
:HEADER2 (fn [v _] [:h2.f3.fw6.f3-ns.lh-title.mt0.mb2 {:id (slugify v)} [:a {:name (slugify v)} v]])
|
||||||
:LINE :p.f5.f5-ns.lh-copy.mt0
|
:LINE :p.f5.f5-ns.lh-copy.mt0
|
||||||
:LINK :a #_link-handler}))
|
:LINK :a #_link-handler}))
|
||||||
|
|
||||||
|
(def theme-toc
|
||||||
|
(merge tachyon-theme
|
||||||
|
{:HEADER1 (fn [v _] [:li [:a {:href (str "#" (slugify v))}
|
||||||
|
[:span.f2.fw6.f3-ns.lh-title.mt0.mb2 v]]])
|
||||||
|
:HEADER2 (fn [v _] [:li [:a {:href (str "#" (slugify v))}
|
||||||
|
[:span.f2.fw6.f3-ns.lh-title.mt0.mb2 v]]])}))
|
||||||
|
|
||||||
;; put constant data here
|
;; put constant data here
|
||||||
(def site-data
|
(def site-data
|
||||||
|
@ -180,7 +190,7 @@
|
||||||
;; Store site state
|
;; Store site state
|
||||||
(defonce site-state (reagent/atom {}))
|
(defonce site-state (reagent/atom {}))
|
||||||
|
|
||||||
;; for one component to render an article
|
;; form one component to render an article
|
||||||
(defn article [{:keys [title description tagline]}]
|
(defn article [{:keys [title description tagline]}]
|
||||||
[:article {:data-name "article-full-bleed-background"}
|
[:article {:data-name "article-full-bleed-background"}
|
||||||
[:div.cf {:style {:background "url(http://placekitten.com/g/600/300)"
|
[:div.cf {:style {:background "url(http://placekitten.com/g/600/300)"
|
||||||
|
@ -233,6 +243,13 @@
|
||||||
[:header.bg-black-90.w-100.ph3.pv3.pv4-ns.ph4-m.ph5-l
|
[:header.bg-black-90.w-100.ph3.pv3.pv4-ns.ph4-m.ph5-l
|
||||||
(into [:nav.f6.fw6.ttu.tracked] (mapv navbar-link links))])
|
(into [:nav.f6.fw6.ttu.tracked] (mapv navbar-link links))])
|
||||||
|
|
||||||
|
(defn footer []
|
||||||
|
[:footer.pv4.ph3.ph5-m.ph6-l.mid-gray.w-full
|
||||||
|
[:a.f6.dib.ph2.link.mid-gray.dim
|
||||||
|
{:target "_blank" :href "https://matrix.to/#/@oly:matrix.org"}
|
||||||
|
"Contact me"]])
|
||||||
|
|
||||||
|
|
||||||
;; form2 component notice the render function takes the same param as the component function
|
;; form2 component notice the render function takes the same param as the component function
|
||||||
;; this is important, you can hit issues if you forget this in form 2 components.
|
;; this is important, you can hit issues if you forget this in form 2 components.
|
||||||
(defn my-component [title value]
|
(defn my-component [title value]
|
||||||
|
@ -278,24 +295,37 @@
|
||||||
:link (rfe/href ::demo {:page "datalog-demo"})
|
:link (rfe/href ::demo {:page "datalog-demo"})
|
||||||
:img-src "https://raw.githubusercontent.com/tonsky/datascript/master/extras/logo.svg"}]}]])
|
:img-src "https://raw.githubusercontent.com/tonsky/datascript/master/extras/logo.svg"}]}]])
|
||||||
|
|
||||||
|
(def toc (partial contains? (into #{} (map keyword [:HEADER1 :HEADER2 :HEDAER3 :HEADER4 :HEADER5 :HEADER6]))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn org->split2
|
||||||
|
"Split out meta and body"
|
||||||
|
[dsl]
|
||||||
|
{:header (filter (fn filter-headers [tag] (headers (first tag))) dsl)
|
||||||
|
:toc (filter (fn filter-headers [tag] (toc (first tag))) dsl)
|
||||||
|
:body (filter (fn filter-body [tag] (body (first tag))) dsl)})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
;; form two component render demo
|
;; form two component render demo
|
||||||
|
;;(h/org->split (parse document1))
|
||||||
(defn demo-page [route]
|
(defn demo-page [route]
|
||||||
(let [demo-key (keyword (-> route :parameters :path :page))
|
(let [demo-key (keyword (-> route :parameters :path :page))
|
||||||
content (reagent/atom {})]
|
content (reagent/atom {})]
|
||||||
(GET (-> site-data :demos demo-key :file)
|
(GET (str "/" (-> site-data :demos demo-key :file))
|
||||||
{:response-format (raw-response-format)
|
{:response-format (raw-response-format)
|
||||||
:handler (fn [response]
|
:handler (fn [response]
|
||||||
;;(prn (org->replacements theme (parse response) ))
|
;;(prn (org->replacements theme (parse response) ))
|
||||||
;;[:LINE "" [:LINK {:href "https://github.com/weavejester/hiccup"} "https://github.com/weavejester/hiccup"]]
|
;;[:LINE "" [:LINK {:href "https://github.com/weavejester/hiccup"} "https://github.com/weavejester/hiccup"]]
|
||||||
(->> response
|
(->> response
|
||||||
parse
|
parse
|
||||||
(org->replacements theme)
|
org->split2
|
||||||
(reset! content)))})
|
(reset! content)))})
|
||||||
(fn [route]
|
(fn [route]
|
||||||
[:main.mt4
|
[:main.mt4
|
||||||
;(org->replacements theme [:LINE "" [:LINK {:href "https://github.com/weavejester/hiccup"} "https://github.com/weavejester/hiccup"]])
|
[:div.mw7.center.avenir (into [:ol] (org->replacements theme-toc (:toc @content)))]
|
||||||
#_(str (org->replacements theme [:LINE "" [:LINK {:href "https://github.com/weavejester/hiccup"} "https://github.com/weavejester/hiccup"]]))
|
[:div.mw7.center.avenir (into [:div] (org->replacements theme (:body @content)))]])))
|
||||||
[:div.mw7.center.avenir (into [:div] @content)]])))
|
|
||||||
|
|
||||||
|
|
||||||
;; form one render about page component
|
;; form one render about page component
|
||||||
|
@ -349,11 +379,11 @@
|
||||||
:my-data "hi"
|
:my-data "hi"
|
||||||
:view home-page}]
|
:view home-page}]
|
||||||
|
|
||||||
["/dsl"
|
["/dsl/"
|
||||||
{:name ::dsl
|
{:name ::dsl
|
||||||
:view dsl-page}]
|
:view dsl-page}]
|
||||||
|
|
||||||
["/about"
|
["/about/"
|
||||||
{:name ::about
|
{:name ::about
|
||||||
:view about-page}]
|
:view about-page}]
|
||||||
|
|
||||||
|
@ -372,7 +402,8 @@
|
||||||
{:href (rfe/href ::about) :text "About" :key "about"}
|
{:href (rfe/href ::about) :text "About" :key "about"}
|
||||||
#_{:href (rfe/href ::i-do-not-exist) :text "missing"}]]
|
#_{:href (rfe/href ::i-do-not-exist) :text "missing"}]]
|
||||||
[:main.mt4
|
[:main.mt4
|
||||||
(when-let [view (-> @site-state :current-route :data :view)] [view (-> @site-state :current-route)])]])
|
(when-let [view (-> @site-state :current-route :data :view)] [view (-> @site-state :current-route)])]
|
||||||
|
[footer]])
|
||||||
|
|
||||||
;; This simply calls reagent render and puts the result in a div with the id of app
|
;; This simply calls reagent render and puts the result in a div with the id of app
|
||||||
;; you can create your own index.html or figwheel provides one with the app id which will replace the default data
|
;; you can create your own index.html or figwheel provides one with the app id which will replace the default data
|
||||||
|
@ -394,7 +425,19 @@
|
||||||
(rf/router routes {:data {:coercion rss/coercion}})
|
(rf/router routes {:data {:coercion rss/coercion}})
|
||||||
(fn [m] (swap! site-state assoc :current-route m))
|
(fn [m] (swap! site-state assoc :current-route m))
|
||||||
;; set to false to enable HistoryAPI
|
;; set to false to enable HistoryAPI
|
||||||
{:use-fragment true})
|
{:use-fragment false
|
||||||
|
:ignore-anchor-click?
|
||||||
|
(fn [router e el uri]
|
||||||
|
;; Add additional check on top of the default checks\
|
||||||
|
(and
|
||||||
|
(ignore-anchor-click? router e el uri)
|
||||||
|
(not (let [href (or (.-href el) "")
|
||||||
|
result (clojure.string/includes? href "#")]
|
||||||
|
#_(when result
|
||||||
|
;(.preventDefault e)
|
||||||
|
#_(js/console.log "will prevent by href" href))
|
||||||
|
result))))
|
||||||
|
})
|
||||||
(render-site))
|
(render-site))
|
||||||
|
|
||||||
;; we defonce the startup so that hot reloading does not reinitialize the state of the site
|
;; we defonce the startup so that hot reloading does not reinitialize the state of the site
|
||||||
|
|
Loading…
Reference in New Issue