Merge remote-tracking branch 'refs/remotes/Day8/master'

This commit is contained in:
Shaun Mahood 2016-08-25 14:40:48 -06:00
commit 6bd664404f
8 changed files with 142 additions and 54 deletions

View File

@ -34,19 +34,19 @@ Assuming your larger apps has multiple "panels" (or "views") which are relativel
``` ```
src src
├── panel1 ├── panel-1
│ ├── db.cljs <--- schema, validation, etc (data layer) │ ├── db.cljs <--- schema, validation, etc (data layer)
│ ├── subs.cljs <--- subscription handlers (query layer) │ ├── subs.cljs <--- subscription handlers (query layer)
│ ├── views.cljs <--- reagent components (view layer) │ ├── views.cljs <--- reagent components (view layer)
│ └── events.cljs <--- event handlers (control/update layer) │ └── events.cljs <--- event handlers (control/update layer)
├── panel2 ├── panel-2
│ ├── db.cljs <--- schema, validation. etc (data layer) │ ├── db.cljs <--- schema, validation. etc (data layer)
│ ├── subs.cljs <--- subscription handlers (query layer) │ ├── subs.cljs <--- subscription handlers (query layer)
│ ├── views.cljs <--- reagent components (view layer) │ ├── views.cljs <--- reagent components (view layer)
│ └── events.cljs <--- event handlers (control/update layer) │ └── events.cljs <--- event handlers (control/update layer)
. .
. .
└── panelN └── panel-n
``` ```
Continue to [Navigation](Navigation.md) to learn how to switch between panels of a larger app. Continue to [Navigation](Navigation.md) to learn how to switch between panels of a larger app.

View File

@ -119,7 +119,7 @@ and inserts its own interceptors
so ACTUALLY, there's about 5 interceptors in the chain. so ACTUALLY, there's about 5 interceptors in the chain.
So, ultimately, that event registration associates the event id `:some-id` So, ultimately, that event registration associates the event id `:some-id`
with a chain of interceptors. with __just__ a chain of interceptors. Nothing more.
Later, when a `(dispatch [:some-id ...])` happens, that 5-chain of Later, when a `(dispatch [:some-id ...])` happens, that 5-chain of
interceptors will be "executed". And that's how events get handled. interceptors will be "executed". And that's how events get handled.
@ -216,7 +216,7 @@ designed by the talented
Dunno about you, but I'm easily offended by underscores. Dunno about you, but I'm easily offended by underscores.
If our components did this: If we had a component which did this:
```clj ```clj
(dispatch [:delete-item 42]) (dispatch [:delete-item 42])
``` ```

View File

@ -1,7 +1,11 @@
## Bootstrapping Your Application State ## Bootstrapping Application State
To bootstrap a re-frame application, you need to: To bootstrap a re-frame application, you need to:
1. register handlers (subscription and event handlers) 1. register handlers
- subscription (via `reg-sub`)
- events (via `reg-event-db` or `reg-event-fx`)
- effects (via `reg-fx`)
- coeffects (via `reg-cofx`)
2. kickstart reagent (views) 2. kickstart reagent (views)
3. Load the right initial data into `app-db` which might be a `merge` of: 3. Load the right initial data into `app-db` which might be a `merge` of:
- Some default values - Some default values
@ -11,23 +15,27 @@ To bootstrap a re-frame application, you need to:
Point 3 is the interesting bit and will be the main focus of this page, but let's work our way through them ... Point 3 is the interesting bit and will be the main focus of this page, but let's work our way through them ...
## Register Event Handlers ## 1. Register Handlers
Generally, there's nothing to do because this happens automatically at (js) script load time, because you declared and registered your event handlers like this: re-frame's multifarious handlers all work in the same way. You declare
and registered your handlers in the one step, like this "event handler" example:
```clj
```Clojure
(re-frame/reg-event-db ;; event handler will be registered automatically (re-frame/reg-event-db ;; event handler will be registered automatically
:some-id :some-id
(fn [db [_ value]] (fn [db [_ value]]
... do some state change based on db and value ... do some state change based on db and value ))
``` ```
## Kick Start Reagent As a result, there's nothing further you need to do because
handler registration happens as a direct result of loading the code
code (presumably via a `<script>`).
## 2. Kick Start Reagent
Create a function `main` which does a `reagent/render` of your root reagent component `main-panel`: Create a function `main` which does a `reagent/render` of your root reagent component `main-panel`:
```Clojure ```clj
(defn main-panel ;; my top level reagent component (defn main-panel ;; my top level reagent component
[] []
[:div "Hello DDATWD"]) [:div "Hello DDATWD"])
@ -38,17 +46,24 @@ Create a function `main` which does a `reagent/render` of your root reagent comp
(js/document.getElementById "app"))) (js/document.getElementById "app")))
``` ```
## Loading Initial Data Mounting the top level component `main-panel` will trigger a cascade of child
component creation. The full DOM tree will be rendered.
Let's rewrite our `main-panel` component to use a subscription: ## 3. Loading Initial Data
Let's rewrite our `main-panel` component to use a subscription. In effect,
we want it to source and render some data held in `app-db`.
First, we'll create the subscription handler:
```Clojure ```Clojure
(re-frame/reg-sub ;; a new subscription handler (re-frame/reg-sub ;; a new subscription handler
:name ;; usage (subscribe [:name]) :name ;; usage (subscribe [:name])
(fn [db _] (fn [db _]
(:name db))) ;; pulls out :name (:display-name db))) ;; extracts `:display-name` from app-db
```
And now we use that subscription:
```clj
(defn main-panel (defn main-panel
[] []
(let [name (re-frame/subscribe [:name])] ;; <--- a subscription <--- (let [name (re-frame/subscribe [:name])] ;; <--- a subscription <---
@ -57,31 +72,33 @@ Let's rewrite our `main-panel` component to use a subscription:
``` ```
The user of our app will see funny things The user of our app will see funny things
if that `(subscribe [:name])` doesn't deliver good data. We must ensure there's good data in `app-db`. if that `(subscribe [:name])` doesn't deliver good data. But how do we ensure "good data"?
That will require: That will require:
1. getting data into `app-db`; and 1. getting data into `app-db`; and
2. not get into trouble if that data isn't yet in `app-db`. For example, the data may have to come from a server and there's latency. 2. not get into trouble if that data isn't yet in `app-db`. For example,
the data may have to come from a server and there's latency.
**Note: `app-db` initially contains `{}`** **Note: `app-db` initially contains `{}`**
### Getting Data Into `app-db` ### Getting Data Into `app-db`
Only event handlers can change `app-db`. Those are the rules!! Even initial values must be put in via an event handler. Only event handlers can change `app-db`. Those are the rules!! Indeed, even initial
values must be put in `app-db` via an event handler.
Here's an event handler for that purpose: Here's an event handler for that purpose:
```Clojure ```Clojure
(re-frame/reg-event-db (re-frame/reg-event-db
:initialise-db ;; usage: (re-frame/dispatch [:initialise-db]) :initialise-db ;; usage: (dispatch [:initialise-db])
(fn [_ _] ;; Ignore both params (db and event) (fn [_ _] ;; Ignore both params (db and event)
{:display-name "DDATWD" ;; return a new value for app-db {:display-name "DDATWD" ;; return a new value for app-db
:items [1 2 3 4]})) :items [1 2 3 4]}))
``` ```
We'll need to dispatch the `:initialise-db` event to get it to execute. `main` seems like the natural place: You'll notice that this handler does nothing other than to return a ` map`. That map
will become the new value within `app-db`.
We'll need to dispatch an `:initialise-db` event to get it to execute. `main` seems like the natural place:
```Clojure ```Clojure
(defn ^:export main (defn ^:export main
[] []
@ -90,13 +107,21 @@ We'll need to dispatch the `:initialise-db` event to get it to execute. `main` s
(js/document.getElementById "app"))) (js/document.getElementById "app")))
``` ```
But remember, event handlers execute async. So although there's a `dispatch` within `main`, the handler for `:initialise-db` will not be run until sometime after `main` has finished. But remember, event handlers execute async. So although there's
a `dispatch` within `main`, the event is simply queued, and the
handler for `:initialise-db`
will not be run until sometime after `main` has finished.
But how long after? And is there a race condition? The component `main-panel` (which needs good data) might be rendered before the `:initialise-db` event handler has put good data into `app-db`. But how long after? And is there a race condition? The
component `main-panel` (which assumes good data) might be
rendered before the `:initialise-db` event handler has
put good data into `app-db`.
We don't want any rendering (of `main-panel`) until after `app-db` is right. We don't want any rendering (of `main-panel`) until after `app-db`
has been correctly initialised.
Okay, so that's enough of teasing-out the issues. Let's see a quick sketch of the entire pattern. It is very straight-forward: Okay, so that's enough of teasing-out the issues. Let's see a
quick sketch of the entire pattern. It is very straight-forward.
## The Pattern ## The Pattern
@ -104,12 +129,12 @@ Okay, so that's enough of teasing-out the issues. Let's see a quick sketch of th
(re-frame/reg-sub ;; the means by which main-panel gets data (re-frame/reg-sub ;; the means by which main-panel gets data
:name ;; usage (subscribe [:name]) :name ;; usage (subscribe [:name])
(fn [db _] (fn [db _]
(:name db))) (:display-name db)))
(re-frame/reg-sub ;; we can check if there is data (re-frame/reg-sub ;; we can check if there is data
:initialised? ;; usage (subscribe [:initialised?]) :initialised? ;; usage (subscribe [:initialised?])
(fn [db _] (fn [db _]
(not (empty? @db)))) ;; do we have data (not (empty? db)))) ;; do we have data
(defn main-panel ;; the top level of our app (defn main-panel ;; the top level of our app
[] []
@ -136,10 +161,10 @@ Okay, so that's enough of teasing-out the issues. Let's see a quick sketch of th
This pattern scales up easily. This pattern scales up easily.
For example, imagine a more complicated scenario in which your app is not fully initialised until 2 backend services supply data. For example, imagine a more complicated scenario in which your app
is not fully initialised until 2 backend services supply data.
Your `main` might look like this: Your `main` might look like this:
```Clojure ```Clojure
(defn ^:export main ;; call this to bootstrap your app (defn ^:export main ;; call this to bootstrap your app
[] []
@ -156,17 +181,43 @@ Your `:initialised?` test then becomes more like this sketch:
(reg-sub (reg-sub
:initialised? ;; usage (subscribe [:initialised?]) :initialised? ;; usage (subscribe [:initialised?])
(fn [db _] (fn [db _]
(and (not (empty? @db)) (and (not (empty? db))
(:service1-answered? @db) (:service1-answered? db)
(:service2-answered? @db))))) (:service2-answered? db)))))
``` ```
This assumes boolean flags are set in `app-db` when data was loaded from these services. This assumes boolean flags are set in `app-db` when data was loaded from these services.
## A Cheat - Synchronous Dispatch ## Cheating - Synchronous Dispatch
In simple cases, you can simplify matters by using `(dispatch-sync [:initialise-db])` in the main entry point function. The [Simple Example](https://github.com/Day8/re-frame/blob/8cf42f57f50f3ee41e74de1754fdb75f80b31775/examples/simple/src/simpleexample/core.cljs#L110) and [TodoMVC](https://github.com/Day8/re-frame/blob/8cf42f57f50f3ee41e74de1754fdb75f80b31775/examples/todomvc/src/todomvc/core.cljs#L35) example both use `dispatch-sync` to initialise the app-db. This causes the event to jump to the front of the line and causes it to execute immediately, which is fine for the initial data load in a simple app but can lead to problems elsewhere. As your app gets more complicated, it is strongly suggested that you use the regular `dispatch` function where possible. If you are using `dispatch-sync` and run into weird errors, there's a pretty high chance that it's the culprit. In simple cases, you can simplify matters by using `(dispatch-sync [:initialise-db])` in
the main entry point function. The
[Simple Example](https://github.com/Day8/re-frame/blob/8cf42f57f50f3ee41e74de1754fdb75f80b31775/examples/simple/src/simpleexample/core.cljs#L110)
and [TodoMVC Example](https://github.com/Day8/re-frame/blob/8cf42f57f50f3ee41e74de1754fdb75f80b31775/examples/todomvc/src/todomvc/core.cljs#L35)
both use `dispatch-sync` to initialise the app-db.
## Services `dispatch` queues an event for later processing, but `dispatch-sync` acts
like a function call and handles an event immediately. That's useful for initial data
load we are considering, particularly for simple apps. Using `dispatch-sync` guarantees
that initial state will be in place before any views are mounted, so we know they'll
subscribe to sensible values. We don't need a guard like `top-panel` (introduced above).
Remember when we used `dispatch` to request the data in our `main` function? What would those event handlers looks like? Let's go to [Talking to Servers](Talking-To-Servers.md) and find out! But don't get into the habit of using `dispatch-sync` everywhere. It is the right
tool in this context and, sometimes, when writing tests, but
`dispatch` is the staple you should use everywhere else.
## Loading Initial Data From Services
Above, in our example `main`, we imagined using `(re-frame/dispatch [:load-from-service-1])` to request data
from a backend services. How would we write the handler for this event?
The next Tutorial will show you how.
---
Previous: [Interceptors](Interceptors.md)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Up: [Index](Readme.md)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Next: [Talking To Servers](Talking-To-Servers.md)

View File

@ -1,13 +1,21 @@
## Namespaced Ids
## What About Event Ids? As an app gets bigger, you'll tend to get clashes on ids - event-ids, or query-ids (subscriptions), etc.
One panel will need to `dispatch` an `:edit` event and so will
another, but the two panels will have different handlers.
So how then to not have a clash? How then to distinguish between
one `:edit` event and another?
As an app gets bigger, you'll tend to get clashes on event ids. One panel will need to `dispatch` an `:edit` event and so will another, but the two panels will have different handlers. Your goal should be to use event-ids which encode both the event
So how then to not have a clash? How then to distinguish between one edit event and another? itself (`:edit` ?) and the context (`:panel1` or `:panel2` ?).
Your goal should be to use event-ids which encode both the event itself (`:edit` ?) and the context (`:panel1` or `:panel2` ?). Luckily, ClojureScript provides a nice easy solution: use keywords
with a __synthetic namespace__. Perhaps something like `:panel1/edit` and `:panel2/edit`.
Luckily, ClojureScript provides a nice easy solution: use keywords with a synthetic namespace. Perhaps something like `:panel1/edit` and `:panel2/edit`. You see, ClojureScript allows the namespace in a keyword to be a total
fiction. I can have the keyword `:panel1/edit` even though
`panel1.cljs` doesn't exist.
You see ClojureScript allows the namespace in a keyword to be a fiction. I can have the keyword `:panel1/edit` even though `panel1.cljs` doesn't exist. Naturally, you'll take advantage of this by using keyword namespaces
which are both unique and descriptive.
Naturally, you'll take advantage of this by using keyword namespaces which are both unique and descriptive.

View File

@ -46,4 +46,4 @@ A high level reagent view has a subscription to :active-panel and will switch to
``` ```
Continue to [Namespaced Keywords](Namespaced-Keywords.md) to reduce clashes on event ids. Continue to [Namespaced Keywords](Namespaced-Keywords.md) to reduce clashes on ids.

BIN
images/logo/Genesis.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

BIN
images/logo/Guggenheim.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -1,5 +1,34 @@
**re-frame logo** ## The re-frame Logo
Created by the mysterious @martinklepsch
Some say he appears on high value stamps in Germany and that he once
punched a horse to the ground. Others say that he loves recursion so much
that in his wallet he keeps a photograph of his wallet.
All we know for certain is that he wields [Sketch.app](https://www.sketchapp.com/) like
Bruce Lee wielded nunchucks.
## Genesis Theories
While we wouldn't presume to fathom the cavernous depths of Martin's creativity, some have
speculated the re-frame logo was created as a bifarious rainbow omage to Frank Lloyd Wright's Guggenheim.
![](Guggenheim.jpg)
<br><br>
Others scoff and insist he smeared the cljs logo across re-frame's official
architecture diagram to form a flowing poststructuralist rebuttal of the tyrannical
"OO" adjacency.
![](Genesis.png)
You be the judge.
Are we better off never knowing? Does art require an answer?
### Instructions
Use [Sketch.app](https://www.sketchapp.com/) to update the `re-frame-logo.sketch` file. Use [Sketch.app](https://www.sketchapp.com/) to update the `re-frame-logo.sketch` file.
Unfortunately the gradients are not exported properly so we can't provide an SVG here for now. Unfortunately the gradients are not exported properly so we can't provide an SVG here for now.