Merge branch 'master' into html5-hist-fragments

This commit is contained in:
Juho Teperi 2019-09-20 10:36:08 +03:00 committed by GitHub
commit f0fef3e525
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 121 additions and 78 deletions

View file

@ -539,7 +539,7 @@ We use [Break Versioning][breakver]. The version numbers follow a `<major>.<mino
(fn [request]
(handler (update request ::acc (fnil conj []) id))))
(defn handler [{:keys [::acc]}]
(defn handler [{::keys [acc]}]
{:status 200, :body (conj acc :handler)})
(def app

View file

@ -18,6 +18,31 @@ request to the server. This means the URL will look normal, but the downside is
that the server must respond to all routes with correct file (`index.html`).
Check examples for simple Ring handler example.
### Anchor click handling
HTML5 History router will handle click events on anchors where the href
matches the route tree (and other [rules](../../modules/reitit-frontend/src/reitit/frontend/history.cljs#L84-L98)).
If you have need to control this logic, for example to handle some
anchor clicks where the href matches route tree normally (i.e. browser load)
you can provide `:ignore-anchor-click?` function to add your own logic to
event handling:
```clj
(rfe/start!
router
{:use-fragment false
:ignore-anchor-click? (fn [router e el uri]
;; Add additional check on top of the default checks
(and (rfh/ignore-anchor-click? router e el uri)
(not= "false" (gobj/get (.-dataset el) "reititHandleClick"))))})
;; Use data-reitit-handle-click to disable Reitit anchor handling
[:a
{:href (rfe/href ::about)
:data-reitit-handle-click false}
"About"]
```
## Easy
Reitit frontend routers require storing the state somewhere and passing it to

View file

@ -17,10 +17,10 @@ controller identity
* `start` & `stop` functions, which are called with controller identity
When you navigate to a route that has a controller, controller identity
is first resolved by calling `identity` function, or by using `parameters`
declaration, or if neither is set, the identity is `nil`. Next controller
is initialized by calling `start` is called with the identity value.
When you exit that route, `stop` is called with the return value of `params.`
is first resolved by using `parameters` declaration, or by calling `identity` function,
or if neither is set, the identity is `nil`. Next, the controller
is initialized by calling `start` with the controller identity value.
When you exit that route, `stop` is called with the last controller identity value.
If you navigate to the same route with different match, identity gets
resolved again. If the identity changes from the previous value, controller

View file

@ -66,7 +66,7 @@ The following produce identical middleware runtime function.
```clj
(require '[reitit.ring :as ring])
(defn handler [{:keys [::acc]}]
(defn handler [{::keys [acc]}]
{:status 200, :body (conj acc :handler)})
(def app

View file

@ -9,7 +9,7 @@ Example middleware to guard routes based on user roles:
(require '[clojure.set :as set])
(defn wrap-enforce-roles [handler]
(fn [{:keys [::roles] :as request}]
(fn [{::keys [roles] :as request}]
(let [required (some-> request (ring/get-match) :data ::roles)]
(if (and (seq required) (not (set/subset? required roles)))
{:status 403, :body "forbidden"}

View file

@ -12,7 +12,7 @@ Below is an example how to do reverse routing from a ring handler:
(ring/ring-handler
(ring/router
[["/users"
{:get (fn [{:keys [::r/router]}]
{:get (fn [{::r/keys [router]}]
{:status 200
:body (for [i (range 10)]
{:uri (-> router

View file

@ -155,7 +155,7 @@ A middleware and a handler:
(fn [request]
(handler (update request ::acc (fnil conj []) id))))
(defn handler [{:keys [::acc]}]
(defn handler [{::keys [acc]}]
{:status 200, :body (conj acc :handler)})
```

View file

@ -155,7 +155,7 @@ Let's reuse the `wrap-enforce-roles` from [Dynamic extensions](dynamic_extension
(s/def ::roles (s/coll-of ::role :into #{}))
(defn wrap-enforce-roles [handler]
(fn [{:keys [::roles] :as request}]
(fn [{::keys [roles] :as request}]
(let [required (some-> request (ring/get-match) :data ::roles)]
(if (and (seq required) (not (set/subset? required roles)))
{:status 403, :body "forbidden"}

View file

@ -12,7 +12,7 @@ There is an extra option in ring-router (actually, in the underlying middleware-
(fn [request]
(handler (update request ::acc (fnil conj []) id))))
(defn handler [{:keys [::acc]}]
(defn handler [{::keys [acc]}]
{:status 200, :body (conj acc :handler)})
(def app

View file

@ -18,7 +18,7 @@
(re-frame/reg-event-fx
::navigate
(fn [db [_ route]]
(fn [db [_ & route]]
;; See `navigate` effect in routes.cljs
{::navigate! route}))
@ -59,8 +59,8 @@
;; Triggering navigation from events.
(re-frame/reg-fx
::navigate!
(fn [k params query]
(rfe/push-state k params query)))
(fn [route]
(apply rfe/push-state route)))
;;; Routes ;;;

View file

@ -2,6 +2,6 @@
:description "Reitit Http App with Swagger"
:dependencies [[org.clojure/clojure "1.10.0"]
[ring/ring-jetty-adapter "1.7.1"]
[aleph "0.4.6"]
[aleph "0.4.7-alpha5"]
[metosin/reitit "0.3.9"]]
:repl-options {:init-ns example.server})

View file

@ -15,7 +15,7 @@
[reitit.http.spec :as spec]
[spec-tools.spell :as spell]
[ring.adapter.jetty :as jetty]
[aleph.http :as client]
[aleph.http :as aleph]
[muuntaja.core :as m]
[clojure.java.io :as io]
[clojure.spec.alpha :as s]
@ -71,7 +71,7 @@
:responses {200 {:body any?}}
:handler (fn [{{{:keys [seed results]} :query} :parameters}]
(d/chain
(client/get
(aleph/get
"https://randomuser.me/api/"
{:query-params {:seed seed, :results results}})
:body
@ -146,6 +146,7 @@
(defn start []
(jetty/run-jetty #'app {:port 3000, :join? false, :async true})
;(aleph/start-server (aleph/wrap-ring-async-handler #'app) {:port 3000})
(println "server running in port 3000"))
(comment

View file

@ -4,4 +4,4 @@
[ring/ring-jetty-adapter "1.7.1"]
[metosin/reitit "0.3.9"]]
:repl-options {:init-ns example.server}
:profiles{:dev {:dependencies [[ring/ring-mock "0.3.2"]]}})
:profiles {:dev {:dependencies [[ring/ring-mock "0.3.2"]]}})

View file

@ -70,7 +70,7 @@
(-> request :muuntaja/request :format))
;; TODO: support faster key walking, walk/keywordize-keys is quite slow...
(defn request-coercer [coercion type model {:keys [::extract-request-format ::parameter-coercion]
(defn request-coercer [coercion type model {::keys [extract-request-format parameter-coercion]
:or {extract-request-format extract-request-format-default
parameter-coercion default-parameter-coercion}}]
(if coercion

View file

@ -33,7 +33,7 @@
#?(:clj clojure.lang.Keyword
:cljs cljs.core.Keyword)
(into-interceptor [this data {:keys [::registry] :as opts}]
(into-interceptor [this data {::keys [registry] :as opts}]
(if-let [interceptor (if registry (registry this))]
(into-interceptor interceptor data opts)
(throw
@ -108,7 +108,7 @@
(chain interceptors nil nil))
([interceptors data]
(chain interceptors data nil))
([interceptors data {:keys [::transform] :or {transform identity} :as opts}]
([interceptors data {::keys [transform] :or {transform identity} :as opts}]
(let [transform (if (vector? transform) (apply comp (reverse transform)) transform)]
(->> interceptors
(keep #(into-interceptor % data opts))
@ -119,7 +119,7 @@
(defn compile-result
([route opts]
(compile-result route opts nil))
([[_ {:keys [interceptors handler] :as data}] {:keys [::queue] :as opts} _]
([[_ {:keys [interceptors handler] :as data}] {::keys [queue] :as opts} _]
(let [chain (chain (into (vec interceptors) [handler]) data opts)]
(map->Endpoint
{:interceptors chain

View file

@ -17,7 +17,7 @@
#?(:clj clojure.lang.Keyword
:cljs cljs.core.Keyword)
(into-middleware [this data {:keys [::registry] :as opts}]
(into-middleware [this data {::keys [registry] :as opts}]
(if-let [middleware (if registry (registry this))]
(into-middleware middleware data opts)
(throw
@ -83,7 +83,7 @@
(if scope {:scope scope})))))
(defn- expand-and-transform
[middleware data {:keys [::transform] :or {transform identity} :as opts}]
[middleware data {::keys [transform] :or {transform identity} :as opts}]
(let [transform (if (vector? transform) (apply comp (reverse transform)) transform)]
(->> middleware
(keep #(into-middleware % data opts))

View file

@ -123,7 +123,7 @@
(->Problem p nil d spec problems)))
(keep identity) (seq) (vec))))
(defn validate [routes {:keys [spec ::wrap] :or {spec ::default-data, wrap identity}}]
(defn validate [routes {:keys [spec] ::keys [wrap] :or {spec ::default-data, wrap identity}}]
(when-let [problems (validate-route-data routes wrap spec)]
(exception/fail!
::invalid-route-data

View file

@ -67,6 +67,29 @@
(first (.composedPath original-event))
(.-target event))))
(defn ignore-anchor-click?
"Precicate to check if the anchor click event default action
should be ignored. This logic will ignore the event
if anchor href matches the route tree, and in this case
the page location is updated using History API."
[router e el uri]
(let [current-domain (if (exists? js/location)
(.getDomain (.parse Uri js/location)))]
(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))
(or (not (.hasAttribute el "target"))
(contains? #{"" "_self"} (.getAttribute el "target")))
;; Left button
(= 0 (.-button e))
;; isContentEditable property is inherited from parents,
;; so if the anchor is inside contenteditable div, the property will be true.
(not (.-isContentEditable el))
(reitit/match-by-path router (.getPath uri)))))
(defrecord Html5History [on-navigate router listen-key click-listen-key]
History
(-init [this]
@ -74,39 +97,24 @@
(fn [e]
(-on-navigate this (-get-path this)))
current-domain
(if (exists? js/location)
(.getDomain (.parse Uri js/location)))
ignore-anchor-click-predicate (or (:ignore-anchor-click? this)
ignore-anchor-click?)
;; Prevent document load when clicking a elements, if the href points to URL that is part
;; of the routing tree."
ignore-anchor-click
(fn ignore-anchor-click
[e]
;; Returns the next matching ancestor of event target
(when-let [el (closest-by-tag (event-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))
;; isContentEditable property is inherited from parents,
;; so if the anchor is inside contenteditable div, the property will be true.
(not (.-isContentEditable el))
(reitit/match-by-path router (.getPath uri)))
(.preventDefault e)
(let [path (str (.getPath uri)
(when (.hasQuery uri)
(str "?" (.getQuery uri)))
(when (.hasFragment uri)
(str "#" (.getFragment uri))))]
(.pushState js/window.history nil "" path)
(-on-navigate this path))))))]
ignore-anchor-click (fn [e]
;; Returns the next matching ancestor of event target
(when-let [el (closest-by-tag (event-target e) "a")]
(let [uri (.parse Uri (.-href el))]
(when (ignore-anchor-click-predicate router e el uri)
(.preventDefault e)
(let [path (str (.getPath uri)
(when (.hasQuery uri)
(str "?" (.getQuery uri)))
(when (.hasFragment uri)
(str "#" (.getFragment uri))))]
(.pushState js/window.history nil "" path)
(-on-navigate this path))))))]
(-on-navigate this (-get-path this))
(assoc this
:listen-key (gevents/listen js/window goog.events.EventType.POPSTATE handler false)
@ -135,15 +143,24 @@
- on-navigate Function to be called when route changes. Takes two parameters, ´match´ and ´history´ object.
Options:
- :use-fragment (default true) If true, onhashchange and location hash are used to store current route."
- :use-fragment (default true) If true, onhashchange and location hash are used to store current route.
Options (Html5History):
- :ignore-anchor-click? Function (router, event, anchor element, uri) which will be called to
check if the anchor click event should be ignored.
To extend built-in check, you can call `reitit.frontend.history/ignore-anchor-click?`
function, which will ignore clicks if the href matches route tree."
([router on-navigate]
(start! router on-navigate nil))
([router
on-navigate
{:keys [use-fragment]
:or {use-fragment true}}]
(let [opts {:router router
:on-navigate on-navigate}]
:or {use-fragment true}
:as opts}]
(let [opts (-> opts
(dissoc :use-fragment)
(assoc :router router
:on-navigate on-navigate))]
(-init (if use-fragment
(map->FragmentHistory opts)
(map->Html5History opts))))))

View file

@ -13,7 +13,7 @@
(update acc method expand opts)
acc)) data ring/http-methods)])
(defn compile-result [[path data] {:keys [::default-options-handler] :as opts}]
(defn compile-result [[path data] {::keys [default-options-handler] :as opts}]
(let [[top childs] (ring/group-keys data)
childs (cond-> childs
(and (not (:options childs)) (not (:handler top)) default-options-handler)
@ -127,7 +127,7 @@
(dissoc :data) ; data is already merged into routes
(cond-> (seq interceptors)
(update-in [:data :interceptors] (partial into (vec interceptors)))))
router (reitit.http/router (r/routes router) router-opts)
router (reitit.http/router (r/routes router) router-opts) ;; will re-compile the interceptors
enrich-request (ring/create-enrich-request inject-match? inject-router?)
enrich-default-request (ring/create-enrich-default-request inject-router?)]
(with-meta

View file

@ -24,7 +24,7 @@
(update :request dissoc ::r/match ::r/router)))
(defn- handle [name stage]
(fn [{:keys [::original ::previous] :as ctx}]
(fn [{::keys [previous] :as ctx}]
(let [current (polish ctx)
previous (polish previous)]
(printer/print-doc (diff-doc stage name previous current) printer)

View file

@ -18,13 +18,13 @@
(defn polish [request]
(dissoc request ::r/match ::r/router ::original ::previous))
(defn printed-request [name {:keys [::original ::previous] :as request}]
(defn printed-request [name {::keys [previous] :as request}]
(printer/print-doc (diff-doc :request name (polish previous) (polish request)) printer)
(-> request
(update ::original (fnil identity request))
(assoc ::previous request)))
(defn printed-response [name {:keys [::original ::previous] :as response}]
(defn printed-response [name {::keys [previous] :as response}]
(printer/print-doc (diff-doc :response name (polish previous) (polish response)) printer)
(-> response
(update ::original (fnil identity response))

View file

@ -49,7 +49,7 @@
Executor
(queue [_ interceptors]
(->> interceptors
(map (fn [{:keys [::interceptor/handler] :as interceptor}]
(map (fn [{::interceptor/keys [handler] :as interceptor}]
(or handler interceptor)))
(keep ->interceptor)))
(enqueue [_ context interceptors]

View file

@ -28,7 +28,7 @@
(update acc method expand opts)
acc)) data http-methods)])
(defn compile-result [[path data] {:keys [::default-options-handler] :as opts}]
(defn compile-result [[path data] {::keys [default-options-handler] :as opts}]
(let [[top childs] (group-keys data)
childs (cond-> childs
(and (not (:options childs)) (not (:handler top)) default-options-handler)

View file

@ -9,7 +9,7 @@
(queue [_ interceptors]
(queue/into-queue
(map
(fn [{:keys [::interceptor/handler] :as interceptor}]
(fn [{::interceptor/keys [handler] :as interceptor}]
(or handler interceptor))
interceptors)))
(execute [_ interceptors request]

View file

@ -71,7 +71,7 @@
"Create a ring handler to emit swagger spec. Collects all routes from router which have
an intersecting `[:swagger :id]` and which are not marked with `:no-doc` route data."
(fn create-swagger
([{:keys [::r/router ::r/match :request-method]}]
([{::r/keys [router match] :keys [request-method]}]
(let [{:keys [id] :or {id ::default} :as swagger} (-> match :result request-method :data :swagger)
ids (trie/into-set id)
strip-top-level-keys #(dissoc % :id :info :host :basePath :definitions :securityDefinitions)

View file

@ -11,7 +11,7 @@
(defn interceptor [name]
{:enter (fn [ctx] (update-in ctx [:request ::i] (fnil conj []) name))})
(defn handler [{:keys [::i]}]
(defn handler [{::keys [i]}]
{:status 200 :body (conj i :ok)})
(deftest http-router-test
@ -89,7 +89,7 @@
(is (= name (-> (r/match-by-name router name) :data :name))))))))
(def enforce-roles-interceptor
{:enter (fn [{{:keys [::roles] :as request} :request :as ctx}]
{:enter (fn [{{::keys [roles] :as request} :request :as ctx}]
(let [required (some-> request (http/get-match) :data ::roles)]
(if (and (seq required) (not (set/intersection required roles)))
(-> ctx
@ -280,7 +280,7 @@
(let [interceptor (fn [name] {:name name
:enter (fn [ctx]
(update-in ctx [:request ::i] (fnil conj []) name))})
handler (fn [{:keys [::i]}] {:status 200 :body (conj i :ok)})
handler (fn [{::keys [i]}] {:status 200 :body (conj i :ok)})
request {:uri "/api/avaruus" :request-method :get}
create (fn [options]
(http/ring-handler
@ -492,14 +492,14 @@
(testing "1-arity"
((http/ring-handler
(http/router [])
(fn [{:keys [::r/router]}]
(fn [{::r/keys [router]}]
(is router))
{:executor sieppari/executor})
{}))
(testing "3-arity"
((http/ring-handler
(http/router [])
(fn [{:keys [::r/router]}]
(fn [{::r/keys [router]}]
(is router))
{:executor sieppari/executor})
{} ::respond ::raise)))

View file

@ -19,7 +19,7 @@
(mw handler (keyword (str name "_" name2 "_" name3))))
(defn handler
([{:keys [::mw]}]
([{::keys [mw]}]
{:status 200 :body (conj mw :ok)})
([request respond _]
(respond (handler request))))
@ -119,7 +119,7 @@
(is (= name (-> (r/match-by-name router name) :data :name))))))))
(defn wrap-enforce-roles [handler]
(fn [{:keys [::roles] :as request}]
(fn [{::keys [roles] :as request}]
(let [required (some-> request (ring/get-match) :data ::roles)]
(if (and (seq required) (not (set/intersection required roles)))
{:status 403, :body "forbidden"}
@ -399,7 +399,7 @@
:wrap (fn [handler]
(fn [request]
(handler (update request ::mw (fnil conj []) name))))})
handler (fn [{:keys [::mw]}] {:status 200 :body (conj mw :ok)})
handler (fn [{::keys [mw]}] {:status 200 :body (conj mw :ok)})
request {:uri "/api/avaruus" :request-method :get}
create (fn [options]
(ring/ring-handler
@ -583,13 +583,13 @@
(testing "1-arity"
((ring/ring-handler
(ring/router [])
(fn [{:keys [::r/router]}]
(fn [{::r/keys [router]}]
(is router)))
{}))
(testing "3-arity"
((ring/ring-handler
(ring/router [])
(fn [{:keys [::r/router]} _ _]
(fn [{::r/keys [router]} _ _]
(is router)))
{} ::respond ::raise)))