From ec1463ed001f6fad9371243c98d19c24045ff710 Mon Sep 17 00:00:00 2001 From: Juho Teperi Date: Tue, 4 Dec 2018 10:51:17 +0200 Subject: [PATCH] Improved frontend docs --- doc/frontend/basics.md | 33 +++++++++++++---- doc/frontend/browser.md | 6 ++-- doc/frontend/controllers.md | 70 +++++++++++++++++++++++++++++++++++-- 3 files changed, 97 insertions(+), 12 deletions(-) diff --git a/doc/frontend/basics.md b/doc/frontend/basics.md index d7330883..3c57b83f 100644 --- a/doc/frontend/basics.md +++ b/doc/frontend/basics.md @@ -1,12 +1,31 @@ # Frontend basics -* https://github.com/metosin/reitit/tree/master/examples/frontend +Reitit frontend integration is built from multiple layers: + +- Core functions which are enchanted with few browser specific +features +- [Browser integration](./browser.md) for attaching Reitit to hash-change or HTML5 +history events +- Stateful wrapper for easy use of history integration +- Optional [controller extension](./controllers.md) + +## Core functions `reitit.frontend` provides few useful functions wrapping core functions: -- `match-by-path` version which parses a URI using JavaScript, including -query-string, and also coerces the parameters. -- `router` which compiles coercers by default -- `match-by-name` and `match-by-name!` with optional `path-paramers` and -logging errors to `console.warn` instead of throwing errors (to prevent -React breaking due to errors). +`match-by-path` version which parses a URI using JavaScript, including +query-string, and also [coerces the parameters](../coercion/coercion.md). +Coerced parameters are stored in match `:parameters` property. If coercion +is not enabled, the original parameters are stored in the same property, +to allow the same code to read parameters regardless if coercion is +enabled. + +`router` which compiles coercers by default. + +`match-by-name` and `match-by-name!` with optional `path-paramers` and +logging errors to `console.warn` instead of throwing errors to prevent +React breaking due to errors. + +## Next + +[Browser integration](./browser.md) diff --git a/doc/frontend/browser.md b/doc/frontend/browser.md index 599f2765..210e5711 100644 --- a/doc/frontend/browser.md +++ b/doc/frontend/browser.md @@ -21,5 +21,7 @@ Check examples for simple Ring handler example. ## Easy Reitit frontend routers require storing the state somewhere and passing it to -all the calls. Wrapper (`reitit.frontend.easy`) is provided which manages -router instance and passes the instance to all calls. +all the calls. Wrapper `reitit.frontend.easy` is provided which manages +a router instance and passes the instance to all calls. This should +allow easy use in most applications, as browser anyway can only have single +event handler for page change events. diff --git a/doc/frontend/controllers.md b/doc/frontend/controllers.md index 999205a2..ac20ade4 100644 --- a/doc/frontend/controllers.md +++ b/doc/frontend/controllers.md @@ -7,20 +7,84 @@ Controllers run code when a route is entered and left. This can be useful to: - Load resources - Update application state -## Authentication +Controller is map of: + +- All properties are optional +- `:params` function, `match` -> controller parameters +- `:start` & `:stop` functions + +Controllers are defined in [route data](../basics/route_data.md) and +as vectors, like: + +``` +["/items" + {:name :items + :controllers [{:start start-fn, :stop stop-fn)}]}] +``` + +To built hierarchies of controllers, nested route data can be used, +due to how Reitit merges the route data for nested routes the vectors are +concatenated: + +``` +["/items" + {:controllers [{:start start-common-fn}]} ;; A + ["" + {:name :items + :controllers [{:start start-items-fn}]}] ;; B + ["/:id" + {:name :item + ;; C + :controllers [{:params item-param-fn :start start-item-fn}]}]] +``` + +In this example both `:items` and `:item` routes inherit `:controllers` from +their parent route. Lets call these controllers A, B and C. `:items` route +has controllers A and B and `:item` A and C. + +Controller `:start` is called when new matches controllers list contains a +controller that is not yet initialized. If either of routes is entered +from some other route, `:start` is called for both of routes controllers. +If `:item` is entered from `:items` only C `:start` is called, because +A controller is identical for both routes. And transition from `:item` +back to `:items` would only call B `:start`. + +Controller `:stop` function is called for controllers that were +initialized but aren't included in the new match. + +To reinitialize a controller when route itself doesn't change, +controllers can provide `:params` function to control their +identity based on which parameters they are interested in. + +To make C controller depend on routes `id` path parameter, it should +provide following function: + +```clj +(defn item-param-fn [match] + (select-keys (:path (:parameters match)) [:id])) +``` + +Now, whenever the result value of this function changes, the identity +of controller changes and controller is stopped and started. + +TODO: Provide way to declare params as data. + +## Tips + +### Authentication Controllers can be used to load resources from a server. If and when your API requires authentication you will need to implement logic to prevent controllers trying to do requests if user isn't authenticated yet. -### Run controllers and check authentication +#### Run controllers and check authentication If you have both unauthenticated and authenticated resources, you can run the controllers always and then check the authentication status on controller code, or on the code called from controllers (e.g. re-frame event handler). -### Disable controllers until user is authenticated +#### Disable controllers until user is authenticated If all your resources require authentication an easy way to prevent bad requests is to enable controllers only after authentication is done.