Merge branch 'develop' into patch-1

This commit is contained in:
Mike Thompson 2017-07-16 19:44:28 +10:00 committed by GitHub
commit eb5c55da68
27 changed files with 707 additions and 480 deletions

View File

@ -1,6 +1,24 @@
## Unreleased
- Fix `re-frame.std-interceptors/on-changes` to work even if effect handler does not set `:db`. [#341](https://github.com/Day8/re-frame/pull/341)
#### New
- improved [testing docs](https://github.com/Day8/re-frame/blob/master/docs/Testing.md)
- added [a new mental model](https://github.com/Day8/re-frame/blob/master/docs/MentalModelOmnibus.md#dsls-and-vms)
#### Fixes
- [#357](https://github.com/Day8/re-frame/pull/357)
- [#340](https://github.com/Day8/re-frame/pull/340)
- [#341](https://github.com/Day8/re-frame/pull/341) Fix `re-frame.std-interceptors/on-changes` to work even if effect handler does not set `:db`.
## 0.9.4 (2017.06.01)
#### Improvements
- added a CITATION.md file
- re-frame now supports self-hosted ClojureScript at an alpha/unofficial/experimental level. It may be removed in the future if it causes problems elsewhere. [#325](https://github.com/Day8/re-frame/pull/325)
## 0.9.3 (2017.05.15)
#### Breaking (previously undefined behaviour)
@ -12,7 +30,7 @@
- Fix ns form in `re-frame.interceptor`. Thanks to [@ggeoffrey](https://github.com/ggeoffrey). [#338](https://github.com/Day8/re-frame/pull/338)
- Even more spelling fixes.
## 0.9.2 (2016.02.09)
## 0.9.2 (2017.02.09)
#### Improvements
@ -32,7 +50,7 @@
## 0.9.0 (2016.12.15) - The Dolores Release
Welcome, board members. Dr Ford has created a new [6-part narrative](README.md),
Welcome, board members. Dr Ford has created a new [6-part narrative](README.md),
and Bernard [some infographics](/docs/EventHandlingInfographic.md). Anyone seen Dolores?
#### Headline
@ -50,7 +68,7 @@ and Bernard [some infographics](/docs/EventHandlingInfographic.md). Anyone seen
#### Improvements
- [#200](https://github.com/Day8/re-frame/pull/200) Remove trailing spaces from console logging
- [#200](https://github.com/Day8/re-frame/pull/200) Remove trailing spaces from console logging
- Add `re-frame.loggers/get-loggers` function to well, you know.
- Added experimental tracing features. These are subject to change and remain undocumented at the moment. By default they are disabled, and will be completely compiled out by advanced optimisations. To enable them, set a [`:closure-defines`](https://www.martinklepsch.org/posts/parameterizing-clojurescript-builds.html) key to `{"re_frame.trace.trace_enabled_QMARK_" true}`
- [#223](https://github.com/Day8/re-frame/issues/223) When using `make-restore-fn`, dispose of any subscriptions that were created after the restore function was created.
@ -65,8 +83,8 @@ and Bernard [some infographics](/docs/EventHandlingInfographic.md). Anyone seen
## 0.8.0 (2016.08.19) - The Walnuts Release
Staying on the leading edge of new buzzwords is obviously critical for any framework.
Angular's terrifying faceplant is a sobering reminder to us all.
Staying on the leading edge of new buzzwords is obviously critical for any framework.
Angular's terrifying faceplant is a sobering reminder to us all.
With this release, re-frame's already impressive buzzword muscles
bulge further with new walnuts like "effects", "coeffects", "interceptors"
and "de-duplicated signal graph". I know, right?
@ -112,57 +130,57 @@ Joking aside, this is a substantial release which will change how you use re-fra
- re-frame now supports the notion of Event Handlers accepting coeffects and returning effects. <br>
There's now three kinds of event handlers: `-db`, `-fx` and `-ctx`. <br>
For a tutorial see: https://github.com/Day8/re-frame/tree/develop/docs <br>
For Effect Handler examples see:
For Effect Handler examples see:
1. https://github.com/Day8/re-frame-http-fx
2. https://github.com/Day8/re-frame-forward-events-fx
3. https://github.com/Day8/re-frame-async-flow-fx
- You can now run and debug re-frame tests on the JVM.
Just to be clear: this does not mean you can run re-frame apps on the JVM (there's no React or
Just to be clear: this does not mean you can run re-frame apps on the JVM (there's no React or
Reagent available). But you can debug your event handler tests using full JVM tooling goodness.
@samroberton and @escherize have provided the thought leadership and drive here. They converted
@samroberton and @escherize have provided the thought leadership and drive here. They converted
re-frame to `.cljc`, supplying pluggable interop for both the `js` and `jvm` platforms.
Further, they have worked with @danielcompton to create a library of testing utilities which
Further, they have worked with @danielcompton to create a library of testing utilities which
will hopefully evolve into a nice step forward on both platforms: <br>
https://github.com/Day8/re-frame-test
Work is ongoing in this area.
- the undo/redo features buried in re-frame has been factored out into
Work is ongoing in this area.
- the undo/redo features buried in re-frame has been factored out into
[a standalone library](https://github.com/Day8/re-frame-undo).
undo and redo have been a part of re-frame from the beginning, but they have never officially
been made a part of the API, and have not been documented. So it nice to see it available, and fully
documented.
been made a part of the API, and have not been documented. So it nice to see it available, and fully
documented.
This new library includes [various enhancements](https://github.com/Day8/re-frame-undo#harvesting-and-re-instating)
over that which previously existed, and it works in with effectful handlers described above.
- Middleware is dead, long live Interceptors.
Up until now, re-frame has allowed you to decorate event handlers with
- Middleware is dead, long live Interceptors.
Up until now, re-frame has allowed you to decorate event handlers with
middleware which looked after the cross cutting concerns of
tracing, undo/redo, validation, etc. This has proved a neat and
tracing, undo/redo, validation, etc. This has proved a neat and
successful part of the framework. We thought we were happy.
But recently @steveb8n gave a cljsyd talk on
Pedestal's Interceptor pattern which suddenly transformed them from
arcane to delightfully simple in 20 mins. Interceptors are
But recently @steveb8n gave a cljsyd talk on
Pedestal's Interceptor pattern which suddenly transformed them from
arcane to delightfully simple in 20 mins. Interceptors are
really "middleware via data" rather than "middleware via higher order functions".
So it is another way of doing the same thing, but thanks to @steveb8n
Interceptors appear a more flexible base, and simpler.
Interceptors also dovetail really nicely with the effects and coeffects
story which has emerged in re-frame through this 0.8.0 release.
So it is another way of doing the same thing, but thanks to @steveb8n
Interceptors appear a more flexible base, and simpler.
Interceptors also dovetail really nicely with the effects and coeffects
story which has emerged in re-frame through this 0.8.0 release.
Docs: https://github.com/Day8/re-frame/tree/develop/docs
- we now have a logo designed by Sketch Maester @martinklepsch. Thank you Martin! But remember, no
- we now have a logo designed by Sketch Maester @martinklepsch. Thank you Martin! But remember, no
good deed ever goes unpunished - we'll be pestering you every time from now on :-)
#### Breaking
@ -171,30 +189,30 @@ Joking aside, this is a substantial release which will change how you use re-fra
- `re-frame.core/register-handler` has been renamed `re-frame.core/reg-event-db`. There's now
three kinds of event-handlers, `-db`, `-fx` and `-ctx`. Event handlers of the 2nd and 3rd kinds
should be registered via the new registration functions `re-frame.core/reg-event-fx` and
should be registered via the new registration functions `re-frame.core/reg-event-fx` and
`re-frame.core/reg-event-ctx`
- `re-frame.core/register-sub` has been renamed `re-frame.core/reg-sub-raw`. This is to indicate that
this kind of registration is now considered the low level, close to the metal way to
create subscriptions handlers. This release introduced `reg-sub` which becomes the preferred way
to register subscription handlers.
- middlewares have been replaced by Interceptors. In day to day use, there's a good
chance you won't notice the change UNLESS:
- middlewares have been replaced by Interceptors. In day to day use, there's a good
chance you won't notice the change UNLESS:
1. You have written your own middleware. If so, you'll have to rewrite it.
See how [the builtin interceptors are done](https://github.com/Day8/re-frame/blob/develop/src/re_frame/std_interceptors.cljc).
2. You explicitly use `comp` to compose middleware like this:
See how [the builtin interceptors are done](https://github.com/Day8/re-frame/blob/develop/src/re_frame/std_interceptors.cljc).
2. You explicitly use `comp` to compose middleware like this:
```clj
(reg-event-db
(reg-event-db
:some-id
(comp debug tim-v) ;; <-- change to [debug trim-v]
(fn [db event]
(fn [db event]
...))
```
- if you have previously used the undo/redo capabilities buried in re-frame, be aware they have
- if you have previously used the undo/redo capabilities buried in re-frame, be aware they have
extracted into a sibling library: https://github.com/Day8/re-frame-undo.
- By default, re-frame uses `js/console` functions like `error` and `warn` when logging, but you can
@ -219,7 +237,7 @@ Joking aside, this is a substantial release which will change how you use re-fra
#### Improvements
- Bug fix: `post-event-callbacks` were not called when `dispatch-sync` was called.
- added new API `re-frame.core/clear-post-event-callback` which de-registers a callback
- added new API `re-frame.core/clear-post-event-callback` which de-registers a callback
previously added by `re-frame.core/add-post-event-callback`
- when an event-handler makes no change to `app-db`, the `debug` middleware now logs a
single line saying so, rather than a "group". Makes it slightly easier to grok
@ -429,5 +447,3 @@ call it, like this `(undoable "Some explanation")`
The `explanation` provided to undoable must be either a `string` (static
explanation) or a function `(db event) -> string`, allowing you to customize
the undo message based on details of the event.

15
CITATION.md Normal file
View File

@ -0,0 +1,15 @@
To cite re-frame in publications, please use:
[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.801613.svg)](https://doi.org/10.5281/zenodo.801613)
Thompson, M. (2015, March). Re-Frame: A Reagent Framework For Writing SPAs, in Clojurescript.
Zenodo. http://doi.org/10.5281/zenodo.801613
@misc{thompson_2015,
author = {Thompson, Michael},
title = {Re-Frame: A Reagent Framework For Writing SPAs, in Clojurescript.},
month = mar,
year = 2015,
doi = {10.5281/zenodo.801613},
url = {https://doi.org/10.5281/zenodo.801613}
}

View File

@ -1,6 +1,6 @@
# Contributing to re-frame
Thank you for taking the time to contribute! Please note that re-frame follows a [code of conduct](code-of-conduct.md).
Thank you for taking the time to contribute! Please note that re-frame follows a [code of conduct](docs/Code-Of-Conduct.md).
## Support questions

291
README.md
View File

@ -18,6 +18,16 @@ y'know. Pretty good.
[![Circle CI](https://circleci.com/gh/Day8/re-frame/tree/develop.svg?style=shield&circle-token=:circle-ci-badge-token)](https://circleci.com/gh/Day8/re-frame/tree/develop)
[![Circle CI](https://circleci.com/gh/Day8/re-frame/tree/master.svg?style=shield&circle-token=:circle-ci-badge-token)](https://circleci.com/gh/Day8/re-frame/tree/master)
## re-frame
re-frame is a pattern for writing [SPAs] in ClojureScript, using [Reagent].
McCoy might report "It's MVC, Jim, but not as we know it". And you would respond
"McCoy, you trouble maker, why even mention an OO pattern?
re-frame is a **functional framework**."
Being a functional framework, it is about data, and the functions
which transform that data.
## Why Should You Care?
@ -32,14 +42,14 @@ Perhaps:
3. You're taking a [Functional Design and Programming course](http://www.eli.sdsu.edu/courses/fall15/cs696/index.html) at San Diego State University
and you have a re-frame/reagent assignment due. You've left the reading a bit late, right?
4. You know Redux, Elm, Cycle.js or Pux and you're
interested in a ClojureScript implementation, **with a data oriented design**.
interested in a ClojureScript implementation.
In this space, re-frame is very old, hopefully in a Gandalf kind of way.
First designed in Dec 2014, it even slightly pre-dates the official Elm Architecture,
although thankfully we were influenced by early-Elm concepts like `foldp` and `lift`, as well as
terrific Clojure projects like [Pedestal App], [Om] and [Hoplon]. Since then,
Clojure projects like [Pedestal App], [Om] and [Hoplon]. Since then,
re-frame has pioneered ideas like event handler middleware,
coeffect accretion, and de-duplicated signal graphs.
5. Which is a lovely segue into the most important point: **re-frame is impressively buzzword compliant**. It has reactivity,
5. Which brings us to the most important point: **re-frame is impressively buzzword compliant**. It has reactivity,
unidirectional data flow, pristinely pure functions,
interceptors, coeffects, conveyor belts, statechart-friendliness (FSM)
and claims an immaculate hammock conception. It also has a charming
@ -50,18 +60,37 @@ Perhaps:
[Hoplon]:http://hoplon.io/
[Pedestal App]:https://github.com/pedestal/pedestal-app
## re-frame
re-frame is a pattern for writing [SPAs] in ClojureScript, using [Reagent].
## It Leverages Data
McCoy might report "It's MVC, Jim, but not as we know it". And you would respond
"McCoy, you trouble maker, why even mention an OO pattern?
re-frame is a **functional framework**."
You might already know that ClojureScript is a modern lisp, and that
lisps are **homoiconic**. If not, you do now.
Being a functional framework, it is about data, and the (pure) functions
which transform that data.
That homoiconic bit is significant. It means you program in a lisp by creating and
assembling lisp data structures. Dwell on that for a moment. You are **programming in data**.
The functions which later transform data, themselves start as data.
### It is a loop
Clojure programmers place particular
emphasis on the primacy of data. When they aren't re-watching Rich Hickey videos,
and wishing their hair was darker and more curly,
they meditate on aphorisms like **Data is the ultimate in late binding**.
I cannot stress enough what a big deal this is. It may seem
like a syntax curiosity at first but, when the penny drops for
you on this, it tends to be a profound moment. And once you
understand the importance of this concept at the language level,
you naturally want to leverage similar power at the library level.
So, it will come as no surprise, then, to know that re-frame has a
data oriented design. Events are data. Effects are data. DOM is data.
The functions which transform data are registered and looked up via
data. Interceptors (data) are preferred over middleware (higher
order functions). Etc.
**Data - that's the way we roll.**
## It is a loop
Architecturally, re-frame implements "a perpetual loop".
@ -100,11 +129,17 @@ you to understand re-frame, is **practically proof** it does physics.
Computationally, each iteration of the loop involves a
six domino cascade.
One domino triggers the next, which triggers the next, et cetera, until we are
back at the beginning of the loop, whereupon the dominoes spring to attention
One domino triggers the next, which triggers the next, et cetera, boom, boom, boom, until we are
back at the beginning of the loop, and the dominoes spring to attention
again, ready for the next iteration of the same cascade.
The six dominoes are ...
The six dominoes are:
1. Event dispatch
2. Event handling
3. Effect handling
4. Query
5. View
6. DOM
### 1st Domino - Event Dispatch
@ -143,19 +178,19 @@ marvelous because they move the app forward. Without them,
an app stays stuck in one state forever, never achieving anything.
So re-frame embraces the protagonist nature of `effects` - the entire, unruly zoo of them - but
it does so in a largely hidden way, and in a manner which is debuggable, auditable, mockable and pluggable.
it does so in a controlled and largely hidden way, and in a manner which is debuggable, auditable, mockable and pluggable.
### We're At A Pivot Point
### We're Now At A Pivot Point
The world just changed and, very often, one particular part of it: the **application state**.
Domino 3 just changed the world and, very often, one particular part of it: the **application state**.
re-frame's `app state` is held in one place - think of it like you
would an in-memory, central database for the app (details later).
When domino 3 changes this `app state`, it triggers the next part of the cascade
Any changes to `app state` trigger the next part of the cascade
involving dominoes 4-5-6.
### There's a Formula
### There's a Formula For It
The 4-5-6 domino cascade implements the formula made famous by Facebook's ground-breaking React library:
`v = f(s)`
@ -165,9 +200,11 @@ A view, `v`, is a function, `f`, of the app state, `s`.
Said another way, there are functions `f` that compute which DOM nodes, `v`,
should be displayed to the user when the application is in a given app state, `s`.
Or, differently: **over time**, as `s` changes, `f`
Or, to capture the dynamics we'd say: **over time**, as `s` changes, `f`
will be re-run each time to compute new `v`, forever keeping `v` up to date with the current `s`.
Or, with yet another emphasis: **over time** what is presented to the user changes in response to application state changes.
In our case, domino 3 changes `s`, the application state,
and, in response, dominoes 4-5-6 are concerned with re-running `f` to compute the new `v`
shown to the user.
@ -212,35 +249,33 @@ This is the step in which the hiccup-formatted
"descriptions of required DOM", returned by the view functions of Domino 5, are made real.
The browser DOM nodes are mutated.
### A Cascade Of Simple Functions
**Each of the dominoes you write are simple, pure functions** which
can be described, understood and
tested independently. They take data, transform it and return new data.
The loop itself is very mechanical in operation.
So, there's a regularity, simplicity and
certainty to how a re-frame app goes about its business,
which leads, in turn, to an ease in reasoning and debugging.
### Managing mutation
## Managing mutation
The two sub-cascades 1-2-3 and 4-5-6 have a similar structure.
In each, it is the second to last domino which
computes "descriptions" of mutations required, and it is
computes "data descriptions" of mutations required, and it is
the last domino which does the dirty work and realises these descriptions.
In both cases, you don't need to worry yourself about this dirty work. re-frame looks
after those dominoes.
## Code Fragments For The Dominos
### A Cascade Of Simple Functions
**You'll (mostly) be writing pure functions** which
can be described, understood and
tested independently. They take data, transform it and return new data.
The loop itself is mechanical and predictable in operation.
So, there's a regularity to how a re-frame app goes about its business,
which leads, in turn, to an ease in reasoning and debugging.
## The Dominoes Again - With Code Fragments
<img align="right" src="/images/Readme/todolist.png?raw=true">
So that was the view of re-frame from 60,000 feet. We'll now shift to 30,000 feet
and look again at each domino, but this time with code fragments.
So that was the view of re-frame from 60,000 feet. We'll now shift down to 30,000 feet
and look again at each domino, but this time with code fragments.
**Imagine:** we're working on a SPA which displays a list of items. You have
just clicked the "delete" button next to the 3rd item in the list.
@ -253,61 +288,77 @@ to completely grok the terse code presented below. We're still at 30,000 feet. D
### Code For Domino 1
The delete button for that 3rd item will have an `on-click` handler (function) which looks
like this:
The delete button for that 3rd item will be rendered by a ViewFunction which looks like this:
```clj
#(re-frame.core/dispatch [:delete-item 2486])
(defn delete-button
[item-id]
[:div.garbage-bin
:on-click #(re-frame.core/dispatch [:delete-item item-id])])
```
`dispatch` emits an `event`.
That `on-click` handler uses re-frame's `dispatch` to emit an `event`.
A re-frame `event` is a vector and, in this case,
it has 2 elements: `[:delete-item 2486]`. The first element,
`:delete-item`, is the kind of event. The rest is optional, further data about the
`event` - in this case, my made-up id, `2486`, for the item to delete.
it has 2 elements: `[:delete-item 2486]` (where `2486` in the made-up id for that 3rd item).
The first element of an event vector,
`:delete-item`, is the kind of event. The rest is optional, useful data about the
`event`.
Events express intent in a domain specific way.
They are the language of your re-frame system.
### Code For Domino 2
An `event handler` (function), `h`, is called to
An `event handler` (function), called say `h`, is called to
compute the `effect` of the event `[:delete-item 2486]`.
Earlier, on program startup, `h` would have been
registered for handling `:delete-item` `events` like this:
On app startup, `re-frame.core/reg-event-fx` would have been used to
register this `h` as the handler for `:delete-item` events, like this:
```clj
(re-frame.core/reg-event-fx ;; a part of the re-frame API
:delete-item ;; the kind of event
h) ;; the handler function for this kind of event
h) ;; the handler function for this kind of event
```
`h` is written to take two arguments:
1. a `coeffects` map which contains the current state of the world (including app state)
2. the `event`
It returns a map of `effects` - a description
of how the world should be changed by the event.
2. the `event` to handle
It is the job of `h` to compute how the world should be changed by the event, and
it returns a map of `effects` - a description of the those changes.
Here's a sketch (we are at 30,000 feet):
```clj
(defn h
[{:keys [db]} event] ;; args: db from coeffect, event
(let [item-id (second event)] ;; extract id from event vector
{:db (dissoc-in db [:items item-id])})) ;; effect is change db
(defn h ;; choose a better name like delete-item
[coeffects event] ;; args: db from coeffect, event
(let [item-id (second event) ;; extract id from event vector
db (:db coeffects) ;; extract the current application state
{:db (dissoc-in db [:items item-id])})) ;; effect is change app state
```
re-frame has ways (beyond us here) to inject necessary aspects
re-frame has ways (described in later tutorials) to inject necessary aspects
of the world into that first `coeffects` argument (map). Different
event handlers need to know different things about the world
in order to get their job done. But current "application state"
is one aspect of the world which is invariably needed, and it is made
available by default in the `:db` key.
event handlers need different "things" to get their job done. But
current "application state" is one aspect of the world which is
invariably needed, and it is available by default in the `:db` key.
BTW, here is a more idiomatic rewrite of `h` which uses "destructuring" of the args:
```clj
(defn h
[{:keys [db]} [_ item-id]] ;; <--- new: obtain db and id directly
{:db (dissoc-in db [:items item-id])}) ;; same as before
```
### Code For Domino 3
An `effect handler` (function) actions the `effects` returned by `h`.
Here's what was returned:
Here's what `h` returned:
```clj
{:db (dissoc-in db [:items item-id])}
{:db (dissoc-in db [:items 2486])} ;; db is a map of some structure
```
Each key of the map identifies one kind
of `effect`, and the value for that key supplies further details.
@ -318,63 +369,86 @@ A key of `:db` means to update the app state with the key's value.
This update of "app state" is a mutative step, facilitated by re-frame
which has a built-in `effects handler` for the `:db` effect.
Why the name `:db`? re-frame sees "app state" as something of an in-memory
database. More on that soon.
Why the name `:db`? Well, re-frame sees "app state" as something of an in-memory
database. More on this is a following tutorial.
Just to be clear, if `h` had returned:
```clj
{:wear "velour flares"
:walk [:like :an :Egyptian]}
{:wear {:pants "velour flares" :belt false}
:tweet "Okay, yes, I am Satoshi. #coverblown"}
```
Then the two effects handlers registered for `:wear` and `:walk` would
be called in this domino to action those two effects. And, no, re-frame
does not supply standard effect handlers for them, so you would have had
Then, the two effects handlers registered for `:wear` and `:tweet` would
be called to action those two effects. And, no, re-frame
does not supply standard effect handlers for either, so you would have had
to have written them yourself (see how in a later tutorial).
### Code For Domino 4
Because an effect handler just updated "app state",
a query (function) over this app state is called automatically (reactively),
itself computing the list of items.
Because an effect handler just mutated "application state",
a query (function) over this app state is automatically called (reactively).
This query (function) computes "a materialised view" of the
application state - a version of the application state which is useful to
the next domino, 5.
Remember, we are now within the `v = f(s)` part of the flow, and this
domino is about delivering the right
data (s) to later domino functions (f) which compute DOM (v).
Now, in this particular case, the query function is pretty trivial.
Because the items are stored in app state, there's not a lot
to compute in this case. This
query function acts more like an extractor or accessor:
to compute and, instead, it acts more like an extractor or accessor,
just plucking the list of items out of application state:
```clj
(defn query-fn
[db _] ;; db is current app state
[db v] ;; db is current app state, v the query vector
(:items db)) ;; not much of a materialised view
```
On program startup, such a `query-fn` must be associated with a `query-id`,
(for reasons obvious in the next domino) like this:
(so it can be used via `subscribe` in the next domino) using `re-frame.core/reg-sub`,
like this:
```clj
(re-frame.core/reg-sub ;; part of the re-frame API
:query-items ;; query id
query-fn) ;; query fn
```
Which says "if you see a query (subscribe) for `:query-items`,
Which says "if you see a `(subscribe [:query-items])`, then
use `query-fn` to compute it".
### Code For Domino 5
Because the query function for `:query-items` just re-computed a new value,
any view (function) which subscribes to `:query-items`
is called automatically (reactively) to re-compute DOM.
any view (function) which uses a `(subscribe [:query-items])`
is called automatically (reactively) to re-compute new DOM.
View functions compute a data structure, in hiccup format, describing
the DOM nodes required. In this case, there will be no DOM nodes
for the now-deleted item, obviously, but otherwise the same DOM as last time.
the DOM nodes required. In this case, the view functions will *not* be generating
hiccup for the now-deleted item obviously but, other than this,
the hiccup computed will be the same as last time.
```clj
(defn items-view
[]
(let [items (subscribe [:query-items])] ;; source items from app state
[:div (map item-render @items])) ;; assume item-render already written
[:div (map item-render @items)])) ;; assume item-render already written
```
Notice how `items` is "sourced" from "app state" via `subscribe`.
It is called with a query id to identify what data it needs.
Notice how `items` is "sourced" from "app state" via `re-frame.core/subscribe`.
It is called with a vector argument, and the first element of that vector is
a query-id which identifies the "materialised view" required.
Note: `subscribe` queries can be parameterised. So, in real world apps
you might have this:<br>
`(subscribe [:items "blue"])`
The vector identifies, first, the query, and then
supplies further arguments. You could think of that as
representing `select * from Items where colour="blue"`.
Except there's no SQL available and you would be the one to implement
the more sophisticated `query-fn` capable of handling the
"where" argument. More in later tutorials.
### Code For Domino 6
@ -411,35 +485,8 @@ When building a re-frame app, you:
- write and register query functions which implement nodes in a signal graph (query layer) (domino 4)
- write Reagent view functions (view layer) (domino 5)
## It Leverages Data
You might already know that ClojureScript is a modern lisp, and that
lisps are **homoiconic**. If not, you do now.
The homoiconic bit is significant. It means you program in a lisp by creating and
assembling lisp data structures. Think about that. You are **programming in data**.
The functions which later manipulate data, start as data.
Clojure programmers place particular
emphasis on the primacy of data. When they aren't re-watching Rich Hickey videos,
and wishing their hair was darker and more curly,
they meditate on aphorisms like "Data is the ultimate in late binding".
I cannot stress enough what a big deal this is. It can seem
like a syntax curiosity at first but, when the penny drops for
you on this, it tends to be a profound moment. And once you
understand the importance of this concept at the language level,
you naturally want to leverage similar power at the library level.
So, it will come as no surprise, then, to know that re-frame has a
data oriented design. Events are data. Effects are data. DOM is data.
The functions which transform data are registered and looked up via
data. Interceptors (data) are preferred over middleware (higher
order functions). Etc.
Data - that's the way we roll.
## It is mature and proven in the large
## re-frame is mature and proven in the large
re-frame was released in early 2015, and has since
[been](https://www.fullcontact.com) successfully
@ -470,28 +517,16 @@ and useful 3rd party libraries.
## Where Do I Go Next?
**At this point you
already know 50% of re-frame.** There's detail to fill in, for sure,
but the core concepts, and even basic coding techniques, are now known to you.
At this point, you know 50% of re-frame. The full [docs are here](/docs/README.md).
Next you need to read the other three articles in the [Introduction section](/docs#introduction):
* [Application State](/docs/ApplicationState.md)
* [Code Walkthrough](/docs/CodeWalkthrough.md)
* [Mental Model Omnibus](/docs/MentalModelOmnibus.md)
This will push your knowledge to about 70%. The
final 30% will come incrementally with use, and by reading the other
tutorials (of which there's a few).
You can also experiment with these two examples: <br>
There are two example apps to play with: <br>
https://github.com/Day8/re-frame/tree/master/examples
Use a template to create your own project: <br>
Client only: https://github.com/Day8/re-frame-template <br>
Full Stack: http://www.luminusweb.net/
Use these resources: <br>
And please be sure to review these further resources: <br>
https://github.com/Day8/re-frame/blob/develop/docs/External-Resources.md
### T-Shirt Unlocked

View File

@ -7,3 +7,5 @@ test:
override:
- lein karma-once
- karma start --single-run --reporters junit,dots
- lein cljsbuild once:
pwd: examples/todomvc

View File

@ -30,7 +30,7 @@ Then:
1. `git clone https://github.com/Day8/re-frame.git`
2. `cd re-frame/examples/simple`
3. `lein do clean, figwheel`
4. wait a minute and then open `http://localhost:3449/example.html`
4. wait a minute and then open <http://localhost:3449/example.html>
So, what's just happened? The ClojureScript code under `/src` has been compiled into `javascript` and
put into `/resources/public/js/client.js` which is loaded into `/resources/public/example.html` (the HTML you just opened)

View File

@ -120,7 +120,7 @@ Something like this (this handler is the same as before, except for one detail):
Look at that - my event handler has a new Interceptor! It is injecting the
right key/value pair (`:local-store`)
into `context's` `:coeffeects`, which itself then goes on to be the first argument
into `context's` `:coeffects`, which itself then goes on to be the first argument
to our event handler (`cofx`).
### `inject-cofx`
@ -169,6 +169,7 @@ handler function that injects the right key/value pair.
The function you register will be passed two arguments:
- a `:coeffects` map (to which it should add a key/value pair), and
- optionally, the additional value supplied to `inject-cofx`
and it is expected to return a modified `:coeffects` map.
### Example Of `reg-cofx`

View File

@ -11,17 +11,17 @@ to the next.
You might wonder: is my event handler making the right changes to `app-db`?
During development, the built-in `debug` interceptor can help.
During development, the built-in `re-frame.core/debug` interceptor can help.
It writes to `console.log`:
1. the event being processed, for example: `[:attempt-world-record true]`
2. the changes made to `db` by the handler in processing the event
`debug` uses `clojure.data/diff` to compare `app-db`
before and after the handler ran, showing what changed.
`debug` uses `clojure.data/diff` to compare the value of `app-db`
before and after the handler ran, showing what changed.
[clojure.data/diff returns a triple](https://clojuredocs.org/clojure.data/diff)
, the first two entries of which
`debug` will display in `console.log` (the 3rd says what hasn't changed and isn't interesting).
`debug` will display in `console.log` (the 3rd says what has not changed and isn't interesting).
The output produced by `clojure.data/diff` can take some getting used to,
but you should stick with it -- your effort will be rewarded.
@ -32,7 +32,7 @@ So, you will add this Interceptor like this:
```clj
(re-frame.core/reg-event-db
:some-id
[debug] ;; <---- added here!
[re-frame.core/debug] ;; <---- added here!
some-handler-fn)
```
@ -43,7 +43,7 @@ So, this is better:
```clj
(re-frame.core/reg-event-db
:some-id
[(when ^boolean goog.DEBUG debug)] ;; <---- conditional!
[(when ^boolean goog.DEBUG re-frame.core/debug)] ;; <---- conditional!
some-handler-fn)
```
@ -52,10 +52,10 @@ It will be `true` when the build within `project.clj` is `:optimization :none` a
otherwise.
Ha! I see a problem, you say. In production, that `when` is going to
leave a `nil` in the interceptor vector. So the Interceptor vector will be `[nil]`.
leave a `nil` in the interceptor vector. So the Interceptor vector will be `[nil]`.
Surely that's a problem?
Well, actually, no it isn't. re-frame filters out `nil`.
Well, actually, no it isn't. re-frame filters out any `nil` from interceptor vectors.
### Too Much Repetition - Part 1
@ -88,16 +88,16 @@ or perhaps:
some-handler-fn)
```
So that `specific-interceptor` could be something required for just this one
event handler, and it can be combined the standard ones.
So that `specific-interceptor` could be something required for just this one
event handler, and it can be combined the standard ones.
Wait on! "I see a problem", you say. `standard-interceptors` is a `vector`, and it
is within another `vector` along side `specific-interceptor` - so that's
Wait on! "I see a problem", you say. `standard-interceptors` is a `vector`, and it
is within another `vector` along side `specific-interceptor` - so that's
nested vectors of interceptors!
No problem, re-frame uses `flatten` to take out all the nesting - the
result is a simple chain of interceptors. And also, as we have discussed,
nils are removed.
result is a simple chain of interceptors. And, as we have discussed,
`nil`s are removed.
## 3. Checking DB Integrity
@ -115,19 +115,20 @@ or, perhaps, [a Prismatic Schema](https://github.com/Prismatic/schema).
**Second** a good spec allows you to assert the integrity and correctness of
the data in `app-db`. Because all the data is in one place, that means you
are asserting the integrity of ALL the data in your app, at one time.
are asserting the integrity of ALL the data in your app, at one time. All of it.
When should we do this? Ideally every time a change is made!
When should we do this? Ideally, every time a change is made!
Well, it turns out that only event handlers can change the value in
`app-db`, so only an event handler could corrupt it. So, we'd like to
**recheck the integrity of `app-db` immediately
Well, it turns out that only event handlers can change the value in
`app-db`, so only an event handler could corrupt it. So, we'd like to
**recheck the integrity of `app-db` immediately
after *every* event handler has run**.
This allows us to catch any errors very early, easily assigning blame (to the rouge event handler).
All of it, every time. This allows us to catch any errors very early,
easily assigning blame (to the rouge event handler).
Schemas are typically put into `db.cljs` (see the todomvc example in the re-frame repo). Here's
an example using Prismatic Schema
an example using Prismatic Schema
(although a more modern choice would be to use [Clojure spec](http://clojure.org/about/spec)):
```clj
(ns my.namespace.db
@ -161,28 +162,27 @@ We'll use the built-in `after` Interceptor factory function:
(when ^boolean goog.DEBUG (after db/valid-schema?))]) ;; <-- new
```
Now, the instant a handler messes up the structure of `app-db` you'll be alerted. But this overhead won't be there in production.
Now, the instant a handler messes up the structure of `app-db` you'll be alerted.
But this overhead won't be there in production.
### Too Much Repetition - Part 2
Above we discussed a way of "factoring out" common interceptors into `standard-interceptors`.
Above, we discussed a way of "factoring out" common interceptors into `standard-interceptors`.
But there's a 2nd way to ensure that all event handlers get certain Interceptors:
There's an additional technique we can use to ensure that all event handlers get certain Interceptors:
you write a custom registration function -- a replacement for `reg-event-db` -- like this:
```clj
(defn my-reg-event-db ;; alternative to reg-event-db
([id handler-fn]
(my-reg-event-db id nil handler-fn))
(my-reg-event-db id standard-interceptors handler-fn))
([id interceptors handler-fn]
(re-frame.core/reg-event-db
id
[(when ^boolean goog.DEBUG debug)
(when ^boolean goog.DEBUG (after db/valid-schema?))
interceptors]
[standard-interceptors interceptors]
handler-fn)))
```
Notice that it inserts our two standard Interceptors.
Notice how this registration function inserts our standard interceptors every time.
From now on, you can register your event handlers like this and know that the two standard Interceptors have been inserted:
```clj
@ -193,32 +193,33 @@ From now on, you can register your event handlers like this and know that the tw
### What about the -fx variation?
Above we created `my-reg-event-db` as a new registration function for `-db` handlers.
That's handlers which take `db` and `event` arguments, and return a new `db`.
So, they MUST return a new `db` value - which should be validated.
Above we created `my-reg-event-db` as a new registration function for `-db` handlers.
Now, `-db` handlers take `db` and `event` arguments, and return a new `db`.
So, they MUST return a new `db` value.
But what if we tried to do the same for `-fx` handlers, which return instead
But what if we tried to do the same for `-fx` handlers which, instead, return
an `effects` map which may, or may not, contain a `:db`? Our solution would
have to allow for the absence of a new `db` value (by doing no validity check, because nothing
was being changed).
```clj
(def debug? ^boolean goog.DEBUG)
(def standard-interceptors-fx
[(when debug? debug) ;; as before
(when debug? (after #(if % (db/valid-schema? %)))]) ;; <-- different after
```
and then:
```clj
(defn my-reg-event-fx ;; alternative to reg-event-db
([id handler-fn]
(my-reg-event-db id nil handler-fn))
(my-reg-event-db id standard-interceptors-fx handler-fn))
([id interceptors handler-fn]
(re-frame.core/reg-event-fx
id
[(when ^boolean goog.DEBUG debug)
(when ^boolean goog.DEBUG (after #(if % (db/valid-schema? %))))
interceptors]
[standard-interceptors-fx interceptors]
handler-fn)))
```
Actually, it would probably be better to write an alternative `after` which XXX
TODO: finish thought
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->

View File

@ -4,25 +4,6 @@ This page describes a technique for
debugging re-frame apps. It proposes a particular combination
of tools.
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table Of Contents
- [Know The Beast!](#know-the-beast)
- [re-frame's Step 3](#re-frames-step-3)
- [Observe The Beast](#observe-the-beast)
- [How To Trace?](#how-to-trace)
- [Your browser](#your-browser)
- [Your Project](#your-project)
- [Say No To Anonymous](#say-no-to-anonymous)
- [IMPORTANT](#important)
- [The result](#the-result)
- [Warning](#warning)
- [React Native](#react-native)
- [Appendix A - Prior to V0.8.0](#appendix-a---prior-to-v080)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Know The Beast!
re-frame apps are **event driven**.
@ -37,20 +18,15 @@ When debugging an event driven system, our focus will be step 3.
## re-frame's Step 3
With re-frame, step 3 happens like this:
With re-frame, step 3 happens like a **domino sequence**: an event arrives and
then bang, bang, bang, one domino triggers the next:
- Event dispatch
- Event handling
- Effects handling
- subscription handlers
- view functions
> 3.1. a `(dispatch [:event-id ....])` happens (that's how events are initiated)
> 3.2. an `Event Handler` is run (along with interceptors), changing the value in `app-db`.
> 3.3. one or more `subscriptions` fire (because of 3.2)
> 3.4. `components` rerender (because of 3.3)
Every single event is processed in the same way. Every single one.
It is like a **domino sequence**: an event arrives and
then bang, bang, bang, one domino triggers the next. A delightfully
Every single event is processed in the same way. Every single one. A delightfully
regular environment to understand and debug!
## Observe The Beast
@ -58,7 +34,7 @@ regular environment to understand and debug!
Bret Victor has explained to us the importance of **observability**.
In which case, when we are debugging re-frame, what do we want to observe?
re-frame's four domino process involves *data values flowing in
re-frame's domino process involves *data values flowing in
and out of relatively simple, pure functions*. Derived data flowing.
So, to debug we want to observe:
- which functions are called
@ -203,7 +179,7 @@ takes a lot of RAM.
For example, if your `app-db` was big and complicated, you might use `path`
middleware to "narrow" that part of `app-db` passed into your event handler
because logging all of `app-db` to js/console might take a while (and not
because logging all of `app-db` to `js/console` might take a while (and not
be that useful).
@ -218,7 +194,7 @@ Enable **Debug JS Remotely** to fix this.
## Appendix A - Prior to V0.8.0
If you are using v0.8.0 or later, then you can probably ignore this section.
If you are using v0.8.0 or later, then you can ignore this section.
Prior to v0.8.0, subscriptions were done using `re-frame.core/reg-sub-raw`,
instead of `re-frame.core/reg-sub` (which is now the preferred method).
@ -420,3 +396,8 @@ From @mccraigmccraig we get the following (untested by me, but they look great):
(code-push/sync)
db)
```
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
<!-- END doctoc generated TOC please keep comment here to allow auto update -->

View File

@ -191,7 +191,7 @@ While it might look like you have registered with 2 interceptors,
It silently inserts `do-fx` at the front, and this is a good thing.
The placement of `do-fx` at the beginning of the interceptor chain means
it's `:after` function would be the final act when the chain is executed
its `:after` function would be the final act when the chain is executed
(forwards and then backwards, as described in the Interceptor Tutorial).
In this final act, the `:after` function extracts `:effects` from `context`
@ -282,8 +282,14 @@ registered handlers) to which you can return.
### Summary
XXX
The 4 Point Summary
In note form:
1. Event handlers should only return effect declaratively
2. They return a map like `{:effect1 value1 :effect2 value2}`
3. Keys of this map can refer to builtin effects handlers (see below) or custom ones
4. We use `reg-fx` to register our own effects handlers
### Builtin Effect Handlers
@ -338,7 +344,7 @@ reset! app-db with a new value. Expects a map.
usage:
```clj
{:db {:key1 value1 key2 value2}}
{:db {:key1 value1 :key2 value2}}
```
### External Effects

View File

@ -6,37 +6,24 @@ Please add to this list by submitting a pull request.
### Templates
* [re-frame-template](https://github.com/Day8/re-frame-template) - Generates the client side SPA
* [Luminus](http://www.luminusweb.net) - Generates SPA plus server side.
* [re-natal](https://github.com/drapanjanas/re-natal) - React Native apps.
* [Slush-reframe](https://github.com/kristianmandrup/slush-reframe) - A scaffolding generator for re-frame run using NodeJS. Should work wih re-frame `0.7.0` if used on a project started from the `0.7.0` version of re-frame-template.
* [Slush-reframe](https://github.com/kristianmandrup/slush-reframe) - A scaffolding generator for re-frame run using NodeJS. Based on re-frame `0.7.0`
* [Celibidache](https://github.com/velveteer/celibidache/) - An opinionated starter for re-frame applications using Boot. Based on re-frame `0.7.0`
### Examples and Applications Using re-frame
* [How to create decentralised apps with re-frame and Ethereum](https://medium.com/@matus.lestan/how-to-create-decentralised-apps-with-clojurescript-re-frame-and-ethereum-81de24d72ff5#.b9xh9xnis) - Tutorial with links to code and live example.
* [Elfeed-cljsrn](https://github.com/areina/elfeed-cljsrn) - A mobile client for [Elfeed](https://github.com/skeeto/elfeed) rss reader, built with React Native.
* [Memory Hole](https://github.com/yogthos/memory-hole) - A small issue tracking app written with Luminus and re-frame.
* [Crossed](https://github.com/velveteer/crossed/) - A multiplayer crossword puzzle generator. Based on re-frame `0.7.0`
* [imperimetric](https://github.com/Dexterminator/imperimetric) - Webapp for converting texts with some system of measurement to another, such as imperial to metric.
* [mperimetric](https://github.com/Dexterminator/imperimetric) - Webapp for converting texts with some system of measurement to another, such as imperial to metric.
* [Brave Clojure Open Source](https://github.com/braveclojure/open-source) A site using re-frame, liberator, boot and more to display active github projects that powers [http://open-source.braveclojure.com](http://open-source.braveclojure.com). Based on re-frame `0.6.0`
* [flux-challenge with re-frame](https://github.com/staltz/flux-challenge/tree/master/submissions/jelz) - flux-challenge is "a frontend challenge to test UI architectures and solutions". This is a ClojureScript + re-frame version. Based on re-frame `0.5.0`
* [flux-challenge with re-frame](https://github.com/staltz/flux-challenge/tree/master/submissions/jelz) - "a frontend challenge to test UI architectures and solutions". re-frame `0.5.0`
* [fractalify](https://github.com/madvas/fractalify/) -
An entertainment and educational webapp for creating & sharing fractal images that powers [fractalify.com](http://fractalify.com). Based on re-frame `0.4.1`
* [Angular Phonecat tutorial in re-frame](http://dhruvp.github.io/2015/03/07/re-frame/) - A detailed step-by-step tutorial that ports the Angular Phonecat tutorial to re-frame. Based on re-frame `0.2.0`
* [Braid](https://github.com/braidchat/braid) - A new approach to group chat, designed around conversations and tags instead of rooms.
### Effect and CoEffect Handlers
@ -49,26 +36,27 @@ Please add to this list by submitting a pull request.
* [re-frame-youtube-fx](https://github.com/micmarsh/re-frame-youtube-fx) - YouTube iframe API wrapper
* [re-frame-web3-fx](https://github.com/madvas/re-frame-web3-fx) - Ethereum Web3 API
* [re-frame-google-analytics-fx](https://github.com/madvas/re-frame-google-analytics-fx) - Google Analytics API
* [re-frame-storage](https://github.com/akiroz/re-frame-storage) - Local Storage based persistence
* [re-frame-storage-fx](https://github.com/deg/re-frame-storage-fx) - Another take on Local Storage persistence
### Routing
* (Bidirectional using Silk and Pushy)[https://pupeno.com/2015/08/18/no-hashes-bidirectional-routing-in-re-frame-with-silk-and-pushy/]
* [Bidirectional using Silk and Pushy](https://pupeno.com/2015/08/18/no-hashes-bidirectional-routing-in-re-frame-with-silk-and-pushy/)
### Tools, Techniques & Libraries
* [re-frame-undo](https://github.com/Day8/re-frame-undo) - An undo library for re-frame
* Animation using `react-flip-move` - http://www.upgradingdave.com/blog/posts/2016-12-17-permutation.html
* [re-frame-test](https://github.com/Day8/re-frame-test) - Advanced testing utilities
* [Animation](http://www.upgradingdave.com/blog/posts/2016-12-17-permutation.html) using `react-flip-move`
* [re-frisk](https://github.com/flexsurfer/re-frisk) - A library for visualizing re-frame data and events.
* [re-thread](https://github.com/yetanalytics/re-thread) - A library for running re-frame applications in Web Workers.
* [re-frame-datatable](https://github.com/kishanov/re-frame-datatable) - DataTable UI component built for use with re-frame.
* [Stately: State Machines](https://github.com/nodename/stately) also https://www.youtube.com/watch?v=klqorRUPluw
* [re-frame-test](https://github.com/Day8/re-frame-test) - Integration Testing (not documented)
* [re-learn](https://github.com/oliyh/re-learn) - Data driven tutorials for educating users of your reagent / re-frame app, built with re-frame
* [re-learn](https://github.com/oliyh/re-learn) - Data driven tutorials for educating users of your reagent / re-frame app
### Videos
* [re-frame your ClojureScript applications](https://youtu.be/cDzjlx6otCU) - re-frame presentation given at Clojure/Conj 2016
* [A Video Tour of the Source Code of Ninja Tools](https://carouselapps.com/2015/12/02/tour-of-the-source-code-of-ninja-tools/)
### Server Side Rendering

View File

@ -89,8 +89,8 @@ transforms a `request` in one direction, and, then in the backwards
sweep, it progressively produces a `response`.
In re-frame, the forwards sweep progressively creates the `coeffects`
(inputs to the handler), while the backwards sweep processes the `effects`
(outputs from the handler).
(inputs to the event handler), while the backwards sweep processes the `effects`
(outputs from the event handler).
I'll pause while you read that sentence again. That's the key
concept, right there.

View File

@ -1,6 +1,8 @@
> In a rush? You can get away with skipping this page on the first pass. <br>
> But remember to cycle back to it later. It contains useful insights.<br>
> In a rush? You can skip this tutorial page on a first pass. <br>
> It is quite abstract and it won't directly help you write re-frame code.
> On the other hand, it will considerably deepen your understanding
> of what re-frame is about, so remember to cycle back and read it later.<br>
> Next page: [Effectful Handlers](EffectfulHandlers.md)
## Mental Model Omnibus
@ -28,22 +30,6 @@ then those patterns will repeat themselves. <br>
> -- Robert Pirsig, Zen and the Art of Motorcycle Maintenance
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table Of Contents
- [What is the problem?](#what-is-the-problem)
- [Guiding Philosophy](#guiding-philosophy)
- [It does Event Sourcing](#it-does-event-sourcing)
- [It does a reduce](#it-does-a-reduce)
- [Derived Data All The Way Down](#derived-data-all-the-way-down)
- [It does FSM](#it-does-fsm)
- [Interconnections](#interconnections)
- [Full Stack](#full-stack)
- [What Of This Romance?](#what-of-this-romance)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## What is the problem?
First, we decided to build our SPA apps with ClojureScript, then we
@ -100,8 +86,66 @@ will encourage control logic into all the
wrong places and you'll end up with a tire-fire of an Architecture. <br>
Sincerely, The Self-appointed President of the Cursor Skeptic's Society.
## It does Event Sourcing
## On DSLs and Machines
`Events` are cardinal to re-frame - they're a fundamental organising principle.
Each re-frame app will have a different set of `events` and your job is
to design exactly the right ones for any given app you build. These `events`
will model "intent" - generally the user's. They will be the
"language of the system" and will provide the eloquence.
And they are data.
Imagine we created a drawing application. And then we allowed
someone to use our application, and as they did we captured, into a collection,
the events caused by that user's actions (button clicks, drags, key presses, etc).
The collection of events might look like this:
```cljs
(def collected-events
[
[:clear]
[:new :triangle 1 2 3]
[:select-object 23]
[:rename "a better name"]
[:delete-selection]
....
])
```
Now, consider the following assembly instructions:
```asm
mov eax, ebx
sub eax, 216
mov BYTE PTR [ebx], 2
```
Assembly instructions are represented as data, right? Data which happens to be "executable"
by the right machine - an x86 machine in the case above.
I'd like you to now look back at that collection of events and view it in the
same way - data instructions which can be executed - by the right machine.
Wait. What machine? Well, the Event Handlers you register collectively implement
the "machine" on which these instructions execute. When you register a new event handler,
it is like you are adding to the instruction set of the "machine".
In this repo's README, near the top, I explained that re-frame had a
Data Oriented Design. Typically, that claim means there's a DSL (Domain specific language)
involved and an interpreter for it. As you design your re-frame app,
YOU design a DSL and then YOU provide the machine to execute it.
Summary:
- Events are the assembly language of your app.
- The instructions collectively form a Domain Specific Language (DSL). The language of your system.
- These instructions are data.
- One instruction after another gets executed by your functioning app.
- The Event Handlers you register collectively implement the "machine" on which this DSL executes.
On the subject of DSLs, watch James Reeves' excellent talk (video): [Transparency through data](https://www.youtube.com/watch?v=zznwKCifC1A)
## It does Event Sourcing
How did that error happen, you puzzle, shaking your head ruefully?
What did the user do immediately prior? What
@ -120,7 +164,7 @@ Note: that's all just data. **Pure, lovely loggable data.**
If you have that data, then you can reproduce the error.
re-frame allows you to time travel, even in a production setting.
Install the "checkpoint" state into `app-db`
To find the bug, install the "checkpoint" state into `app-db`
and then "play forward" through the collection of dispatched events.
The only way the app "moves forwards" is via events. "Replaying events" moves you
@ -157,7 +201,7 @@ Then notice that `reg-event-db` event handlers take two arguments also:
Interesting. That's the same as a `combining function` in a `reduce`!!
So now we can introduce the new mental model: at any point in time,
So, now we can introduce the new mental model: at any point in time,
the value in `app-db` is the result of performing a `reduce` over
the entire `collection` of events dispatched in the app up until
that time. The combining function for this reduce is the set of event handlers.
@ -272,7 +316,7 @@ Sometimes, we'd rewrite this code as:
str)
```
With this arrangement, we talk of "threading" data
through functions. **It seems to help our comprehension to frame function
through functions. **It seems to help our comprehension to conceive function
composition in terms of data flow**.
re-frame delivers architecture
@ -334,3 +378,8 @@ Next: [Infographic Overview](EventHandlingInfographic.md)
[OM]:https://github.com/swannodette/om
[Hoplon]:http://hoplon.io/
[Pedestal App]:https://github.com/pedestal/pedestal-app
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
<!-- END doctoc generated TOC please keep comment here to allow auto update -->

View File

@ -20,6 +20,19 @@
- [Correcting a wrong](SubscriptionsCleanup.md)
- [Flow Mechanics](SubscriptionFlow.md)
### Other Tutorials
- [purelyfunctional.tv](https://purelyfunctional.tv/guide/re-frame-building-blocks/) - a excellent, written overview.
- [Lambda Island Videos](https://lambdaisland.com/episodes) - commercial videos on clojure, with some on re-frame
### Reagent Tutorials
- [The Basics](https://github.com/Day8/re-frame/wiki#reagent-tutorials) (look at the bottom of that page)
- [Lambda Island Videos](https://lambdaisland.com/episodes). There's a 3 part series.
- [purelyfunctional.tv ](https://purelyfunctional.tv/guide/reagent/) - a written overview
- [Reagent Deep Dive Series from Timothy Pratley](http://timothypratley.blogspot.com.au/p/p.html) four part series
- [Props, Children & Component Lifecycle](https://www.martinklepsch.org/posts/props-children-and-component-lifecycle-in-reagent.html) by Martin Klepsch
### App Structure
- [Basic App Structure](Basic-App-Structure.md)
@ -38,6 +51,7 @@
- [Debugging Event Handlers](Debugging-Event-Handlers.md)
- [Debugging](Debugging.md)
- [Testing](Testing.md)
### Miscellaneous
@ -50,6 +64,6 @@
- [Code Of Conduct](Code-Of-Conduct.md)
<!-- We put these at the end so that there is nothing for doctoc to generate. -->
<!-- START doctoc -->
<!-- END doctoc -->
<!-- START doctoc -->
<!-- END doctoc -->

View File

@ -71,7 +71,7 @@ Above, I suggested this:
@(rf/subscribe [:time-str])])
```
But that may offend your aesthetics. Too much noise with those `@`?
But that may offend your aesthetics. Too much noise with those two `@`?
To clean this up, we can define a new `listen` function:
```clj
@ -91,6 +91,16 @@ And then rewrite:
So, at the cost of writing your own function, `listen`, the code is now less noisy
AND there's less chance of us forgetting an `@` (which can lead to odd problems).
### LambdaIsland Naming (LIN)
I've ended up quite liking [the alternative names](https://lambdaisland.com/blog/11-02-2017-re-frame-form-1-subscriptions)
suggested by [Lambda Island Videos](https://lambdaisland.com/):
```cljs
(def <sub (comp deref re-frame.core/subscribe)) ;; aka listen (above)
(def >evt re-frame.core/dispatch)
```
### Say It Again
So, if, in code review, you saw this view function:
@ -102,8 +112,8 @@ So, if, in code review, you saw this view function:
```
What would you (supportively) object to?
That `sort`, right? Computation in the view. Instead, we want the right data
delivered to the view - its job is to simply make `hiccup`.
That `sort`, right? Computation in the view. Instead, we want exactly the right data
delivered to the view - no further computation required - the view's job is to simply make `hiccup`.
The solution is to create a subscription that delivers items already sorted.
```clj
@ -134,15 +144,14 @@ Now it is easy to test `item-sorter` independently.
### And There's Another Benefit
re-frame de-duplicates signal graph nodes.
re-frame de-duplicates signal graph nodes.
If, for example, two views wanted to `(subscribe [:sorted-items])` only the one node
(in the signal graph) would be created. Only one node would be doing that
potentially expensive sorting operation (when items changed) and values from
it would be flowing through to both views.
That sort of efficiency can't happen if this views themselves are doing the `sort`.
That sort of efficiency can't happen if this views themselves are doing the `sort`.
### de-duplication

View File

@ -1,136 +1,232 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table Of Contents
- [Testing](#testing)
- [Event Handlers - Part 1](#event-handlers---part-1)
- [Event Handlers - Part 2](#event-handlers---part-2)
- [Subscription Handlers](#subscription-handlers)
- [Components- Part 1](#components--part-1)
- [Components - Part 2A](#components---part-2a)
- [Components - Part 2B](#components---part-2b)
- [Components - Part 2C](#components---part-2c)
- [Summary](#summary)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
> IGNORE THIS DOCUMENT. IT IS WIP
## Testing
This is an introduction to testing re-frame apps. It
walks you through some choices.
## What To Test
With a re-frame app, there's principally three things to test:
1. Event handlers
2. Subscription handlers
3. View functions
For any re-frame app, there's three things to test:
## Event Handlers - Part 1
- **Event Handlers** - most of your testing focus will
be here because this is where most of the logic lives
- **Subscription Handlers** - often not a lot to test here. Only
[Layer 2](SubscriptionInfographic.md) subscriptions need testing.
- **View functions** - I don't tend to write tests for views. There, I said it.
Hey! It is mean to look at someone with that level of disapproval,
while shaking your head. I have my reasons ...<br>
In my experience with the re-frame architecture, View Functions
tend to be an unlikely source of bugs. And every line of code you write is
like a ball & chain you must forevermore drag about, so I dislike maintaining
tests which don't deliver good bang for buck.
Event Handlers are pure functions and consequently easy to test.
And, yes, in theory there's also `Effect Handlers` (Domino 3) to test,
but you'll hardly ever write one, and, anyway, each one is different, so
I've got no good general insight to offer you for them. They will be ignored
in this tutorial.
## Test Terminology
First, create an event handler like this:
Let's establish some terminology to aid the further explanations in this
tutorial. Every unittest has 3 steps:
1. **setup** initial conditions
2. **execute** the thing-under-test
3. **verify** that the thing-under-test did the right thing
## Exposing Event Handlers For Test
Event Handlers are pure functions which should make them easy to test, right?
First, create a named event handler using `defn` like this:
```clj
(defn my-db-handler
[db v]
(defn select-triangle
[db [_ triangle-id]
... return a modified version of db)
```
Then, register it in a separate step:
You'd register this handler in a separate step:
```clj
(re-event-db
:some-id
(re-frame.core/reg-event-db ;; this is a "-db" event handler, not "-fx"
:select-triangle
[some-interceptors]
my-handler)
select-triangle) ;; <--- defn above. don't use an annonomous fn
```
With this setup, `my-db-handler` is available for direct testing.
This arrangement means the event handler function
`select-triangle` is readily available to be unittested.
Your unittests will pass in certain values for `db` and `v`, and then ensure it returns the right (modified version of) `db`.
## Event Handlers - Setup - Part 1
## Event Handlers - Part 2
To test `select-triangle`, a unittest must pass in values for the two arguments
`db` and `v`. And, so, our **setup** would have to construct both values.
Event handlers mutate the value in `app-db` - that's their job.
But how to create a useful `db` value?
I'd recommend defining a [Prismatic Schema](https://github.com/Prismatic/schema)
for the value in `app-db` and then checking for correctness after every,
single event handler. Every single one.
`db` is a map of a certain structure, so one way would be to simply `assoc` values
into a map at certain paths to simulate a real-world `db` value or, even easier, just use
a map literal, like this:
Using `after` middleware, this is easy to arrange. The todomvc example shows how:
- [define a Schema](https://github.com/Day8/re-frame/blob/2ba8914d8dd5f0cf2b09d6f3942823a798c2ef5c/examples/todomvc/src/todomvc/db.cljs#L6-L28) for the value in `app-db`
- [create some middleware](https://github.com/Day8/re-frame/blob/2ba8914d8dd5f0cf2b09d6f3942823a798c2ef5c/examples/todomvc/src/todomvc/handlers.cljs#L11-L19)
- [add the middleware](https://github.com/Day8/re-frame/blob/2ba8914d8dd5f0cf2b09d6f3942823a798c2ef5c/examples/todomvc/src/todomvc/handlers.cljs#L46) to your event handlers
```cljs
;; a test
(let [
;; setup - create db and event
db {:some 42 :thing "hello"} ; a literal
event [:select-triange :other :event :args]
;; execute
result-db (select-triange db event)]
;; validate that result-db is correct)
(is ...)
```
This certainly works in theory, but in practice,
unless we are careful, constructing the `db`
value in **setup** could:
* be manual and time consuming
* tie tests to the internal structure of `app-db`
The **setup** of every test could end up relying on the internal structure
of `app-db` and any change in that structure (which is inevitable over time)
would result in a lot re-work in the tests. That's too fragile.
So, this approach doesn't quite work.
## Event Handlers - Setup - Part 2
> In re-frame, `Events` are central. They are the "language of the system". They
provide the eloquence.
The `db` value (stored in `app-db`) is the cumulative result
of many event handlers running.
We can use this idea. In **setup**, instead of manually trying to create that `db` value, we could
"build up" a `db` value by threading `db` through many event handlers
which cumulatively create the required initial state. Tests then need
know nothing about the internal structure of that `db`.
Like this:
```clj
(let [
;; setup - cummulatively build up db
db (-> {} ;; empty db
(initialise-db [:initialise-db]) ;; each event handler expects db and event
(clear-panel [:clear-panel])
(draw-triangle [:draw-triangle 1 2 3]))
event [:select-triange :other :stuff]
;; now execute the event handler under test
db' (select-triange db event)]
;; validate that db' is correct
(is ...)
```
This approach works so long as all the event handlers are
of the `-db` kind, but the threading gets a little messy when some event
handlers are of the `-fx` kind which take a `coeffect` argument and
return `effects`, instead of a `db` value.
So, this approach is quite workable in some cases, but can get messy
in the general case.
## Event Handlers - Setup - Part 3
There is further variation which is quite general but not as pure.
During test **setup** we could literally just `dispatch` the events
which would put `app-db` into the right state.
Except, we'd have to use `dispatch-sysnc` rather `dispatch` to
force immediate handling of events, rather than queuing.
```clj
;; setup - cummulatively build up db
(dispatch-sync [:initialise-db])
(dispatch-sync [:clear-panel])
(dispatch-sync [:draw-triangle 1 2 3]))
;; execute
(dispatch-sync [:select-triange :other :stuff])
;; validate that the valuein `app-db` is correct
;; perhaps with subscriptions
```
Notes:
1. we use `dispatch-sync` because `dispatch` is async (event is handled not now, but soon)
2. Not pure. We are choosing to mutate the global `app-db`. But
having said that, there's something about this approach with is remarkably
pragmatic.
2. the **setup** is now very natural. The associated handlers can be either `-db` or `-fx`
3. if the handlers have effects other than just updating app-db, we might need to stub out XXX
4. How do we look at the results ????
If this method appeals to you, you should ABSOLUTELY review the utilities in this helper library:
[re-frame-test](https://github.com/Day8/re-frame-test).
In summary, event handlers should be easy to test because they are pure functions. The interesting
part is the unittest "setup" where we need to establishing an initial value for `db`.
## Subscription Handlers
Here's a subscription handler from [the todomvc example](https://github.com/Day8/re-frame/blob/master/examples/todomvc/src/todomvc/subs.cljs):
Here's a Subscription Handler from
[the todomvc example](https://github.com/Day8/re-frame/blob/master/examples/todomvc/src/todomvc/subs.cljs):
```clj
(reg-sub
:completed-count
(fn [db _]
(completed-count (:todos db))))
:visible-todos
;; signal function
(fn [query-v _]
[(subscribe [:todos])
(subscribe [:showing])])
;; computation function
(fn [[todos showing] _] ;; that 1st parameter is a 2-vector of values
(let [filter-fn (case showing
:active (complement :done)
:done :done
:all identity)]
(filter filter-fn todos))))
```
How do we test this?
We could split the handler function from its registration, like this:
First, we could split the computation function from its registration, like this:
```clj
(defn get-completed-count
[app-db _]
(reaction (completed-count (:todos @app-db))))
(defn visible-todos
[[todos showing] _]
(register-sub
:completed-count
get-completed-count)
(let [filter-fn (case showing
:active (complement :done)
:done :done
:all identity)]
(filter filter-fn todos)))
(reg-sub
:visible-todos
(fn [query-v _]
[(subscribe [:todos])
(subscribe [:showing])])
visible-todos) ;; <--- computation function used here
```
That makes `get-completed-count` available for direct testing. But you'll note it isn't a pure function.
It isn't values in, values out. Instead, it is atoms in, atoms out.
That makes `visible-todos` available for direct unit testing.
If this function was on a paint chart, they'd call in "Arctic Fusion" to indicate its
proximity to pure white, while hinting at taints.
## View Functions - Part 1
We could accept this. We could create tests by passing in a `reagent/atom` holding the
certain values and then checking the values in what's returned. That would work.
The more pragmatic among us might even approve.
Components/views are more tricky and there are a few options.
But let's assume that's not good enough. Let's refactor for pureness:
But remember my ugly secret - I don't tend to write tests for my views.
The 1st step in this refactor is to create a pure function which actually does ALL the hard work ...
```clj
(defn completed-count-handler
[db v] ;; db is a value, not an atom
..... return a value here based on the values db and v)
```
But here's how, theoretically, I'd write tests if I wasn't me ...
The 2nd step in the refactor is to register using a thin `reaction` wrapper:
```clj
(register-sub
:completed-count
(fn [app-db v]
(reaction (completed-count-handler @app-db v))))
```
Because `completed-count-handler` is now doing all the work, it is the thing we want
to test, and it is now a pure function. So I think we are there.
## Components- Part 1
Components/views are slightly more tricky. There's a few options.
First, I have to admit an ugly secret. I don't tend to write tests for my views.
Hey, don't give me that disproving frown! I have my reasons.
Remember that every line of code you write is a liability. So tests have to earn
their keep - they have to deliver a good cost / benefit ratio. And, in my experience
with the re-frame architecture, the Reagent view components tend to be an unlikely
source of bugs. There's just not much logic in them for me to get wrong.
Okay, fine, don't believe me, then!!
Here's how, theoretically, I'd write tests if I wasn't me ...
If a Components is a [Form-1](https://github.com/Day8/re-frame/wiki/Creating-Reagent-Components#form-1-a-simple-function) structure, then it is fairly easy to test.
If a View Function is a [Form-1](https://github.com/Day8/re-frame/wiki/Creating-Reagent-Components#form-1-a-simple-function)
structure, then it is fairly easy to test.
A trivial example:
```clj
@ -142,44 +238,45 @@ A trivial example:
;;=> [:div "Hello " "Wiki"]
```
So, here, testing involves passing values into the function and checking the data structure returned for correctness.
So, here, testing involves passing values into the function and checking the data structure returned
for correctness.
What's returned is hiccup, of course. So how do you test hiccup for correctness?
hiccup is just a clojure data structure - vectors containing keywords, and maps, and other vectors, etc. Perhaps you'd use https://github.com/nathanmarz/specter to declaratively check on the presence of certain values and structures? Or do it more manually.
hiccup is just a clojure data structure - vectors containing keywords, and maps, and other vectors, etc.
Perhaps you'd use https://github.com/nathanmarz/specter to declaratively check on the presence
of certain values and structures? Or do it more manually.
## Components - Part 2A
## View Functions - Part 2A
But what if the Component has a subscription (via a [Form-2](https://github.com/Day8/re-frame/wiki/Creating-Reagent-Components#form-2--a-function-returning-a-function) structure)?
But what if the View Function has a subscription?
```clj
(defn my-view
[something]
(let [val (subscribe [:query-id])] <-- reactive subscription
(fn [something] <-- returns the render function
[:div .... using @val in here])))
[:div .... using @val in here])))
```
There's no immediately obvious way to test this as a lovely pure function. Because it is not pure.
The use of `subscribe` makes the function impure (it obtains data from places other than its args).
Of course, less pure ways are very possible. For example, a plan might be:
1. setup `app-db` with some values in the right places (for the subscription)
2. call `my-view` (with a parameter) which will return `the renderer`
3. call `the renderer` (with a parameter) which will return hiccup
4. check the hiccup structure for correctness.
A testing plan might be:
1. setup `app-db` with some values in the right places (via dispatch of events?)
2. call `my-view` (with a parameter) which will return hiccup
3. check the hiccup structure for correctness.
Continuing on, in a second phase you could then:
5. change the value in `app-db` (which will cause the subscription to fire)
6. call `the renderer` again (hiccup returned).
6. call view functions again (hiccup returned).
7. check that the hiccup
Which is all possible, if a little messy, and with one gotcha. After you change the value in `app-db` the subscription won't hold the new value straight away. It won't get calculated until the next animationFrame. And the next animationFrame won't happen until you hand back control to the browser. I think. Untested. Please report back here if you try. And you might also be able to use `reagent.core/flush` to force the view to be updated.
Which is all possible, if a little messy.
## Components - Part 2B
Or ... instead of the not-very-pure method above, you could use `with-redefs` on `subscribe` to stub out re-frame altogether:
## View Functions - Part 2B
There is a very pragmatic method available to handle the impurity: use `with-redefs`
to stub out `subscribe`. Like this:
```clj
(defn subscription-stub [x]
(atom
@ -188,26 +285,29 @@ Or ... instead of the not-very-pure method above, you could use `with-redefs` o
(deftest some-test
(with-redefs [re-frame/subscribe (subscription-stub)]
(testing "some rendering"
..... somehow call or render the component and check the output)))
(testing "some some view which does a subscribe"
..... call the view function and the hiccup output)))
```
For more integration level testing, you can use `with-mounted-component` from the [reagent-template](https://github.com/reagent-project/reagent-template/blob/master/src/leiningen/new/reagent/test/cljs/reagent/core_test.cljs) to render the component in the browser and validate the generated DOM.
For more integration level testing, you can use `with-mounted-component`
from the [reagent-template](https://github.com/reagent-project/reagent-template/blob/master/src/leiningen/new/reagent/test/cljs/reagent/core_test.cljs)
to render the component in the browser and validate the generated DOM.
## Components - Part 2C
## View Functions - Part 2C
Or ... you can structure in the first place for easier testing and pure functions.
Or ... there is another option: you can structure in the first place for pure view functions.
The trick here is to create an outer and inner component. The outer sources the data
(via a subscription), and passes it onto the inner as props (parameters).
(via a subscription), and passes it onto the inner as props (parameters).
As a result, the inner component, which does the testable work, is pure and
easily tested. The outer is fairly trivial.
easily tested. The outer is impure but fairly trivial.
To get a more concrete idea, I'll direct you to another page on this Wiki
To get a more concrete idea, I'll direct you to another page in the docs
which has nothing to do with testing, but it does use this `simple-outer-subscribe-with-complicated-inner-render`
pattern for a different purpose: [[Using-Stateful-JS-Components]]
pattern for a different purpose:
[Using Stateful JS Components](Using-Stateful-JS-Components.md)
Note this technique could be made simple and almost invisible via the
use of macros. (Contribute one if you have it).
@ -217,6 +317,10 @@ it is called the [Container/Component pattern](https://medium.com/@learnreact/co
## Summary
So, we stumbled slightly at the final hurdle with Form-2 Components. But prior
to this, the testing story for re-frame was as good as it gets: you are testing
a bunch of simple, pure functions. No dependency injection in sight!
Event handlers will be your primary focus when testing. Remember to review the utilities in
[re-frame-test](https://github.com/Day8/re-frame-test).
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
<!-- END doctoc generated TOC please keep comment here to allow auto update -->

View File

@ -5,7 +5,7 @@ This tiny application is meant to provide a quick start of the basics of re-fram
A detailed source code walk-through is provided in the docs:
https://github.com/Day8/re-frame/blob/master/docs/CodeWalkthrough.md
All the code is in one namespace: `/src/simpleexample/core.cljs`
All the code is in one namespace: `/src/simple/core.cljs`.
### Run It And Change It

View File

@ -2,7 +2,7 @@
:dependencies [[org.clojure/clojure "1.8.0"]
[org.clojure/clojurescript "1.9.227"]
[reagent "0.6.0-rc"]
[re-frame "0.9.0"]]
[re-frame "0.9.4"]]
:plugins [[lein-cljsbuild "1.1.3"]
[lein-figwheel "0.5.4-7"]]

View File

@ -2,7 +2,7 @@
:dependencies [[org.clojure/clojure "1.8.0"]
[org.clojure/clojurescript "1.9.89"]
[reagent "0.6.0-rc"]
[re-frame "0.9.0"]
[re-frame "0.9.4"]
[binaryage/devtools "0.8.1"]
[secretary "1.2.3"]]

View File

@ -13,7 +13,7 @@
;; can change the value in app-db so, after each event handler
;; has run, we re-check app-db for correctness (compliance with the Schema).
;;
;; How is this done? Look in events.cljs and you'll notice that all handers
;; How is this done? Look in events.cljs and you'll notice that all handlers
;; have an "after" interceptor which does the spec re-check.
;;
;; None of this is strictly necessary. It could be omitted. But we find it

View File

@ -26,7 +26,7 @@
(def ->local-store (after todos->local-store))
;; Each event handler can have its own set of interceptors (middleware)
;; But we use the same set of interceptors for all event habdlers related
;; But we use the same set of interceptors for all event handlers related
;; to manipulating todos.
;; A chain of interceptors is a vector.
(def todo-interceptors [check-spec-interceptor ;; ensure the spec is still valid

View File

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2015-2016 Michael Thompson
Copyright (c) 2015-2017 Michael Thompson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,10 +1,11 @@
(defproject re-frame "0.9.4-SNAPSHOT"
(defproject re-frame "0.9.5-SNAPSHOT"
:description "A Clojurescript MVC-like Framework For Writing SPAs Using Reagent."
:url "https://github.com/Day8/re-frame.git"
:license {:name "MIT"}
:dependencies [[org.clojure/clojure "1.8.0"]
[org.clojure/clojurescript "1.9.227"]
[reagent "0.6.0"]
[net.cgrand/macrovich "0.2.0"]
[org.clojure/tools.logging "0.3.1"]]
:profiles {:debug {:debug true}

View File

@ -85,8 +85,8 @@
:dispatch-n
(fn [value]
(if-not (sequential? value)
(console :error "re-frame: ignoring bad :dispatch-n value. Expected a collection, got got:" value))
(doseq [event value] (router/dispatch event))))
(console :error "re-frame: ignoring bad :dispatch-n value. Expected a collection, got got:" value)
(doseq [event value] (router/dispatch event)))))
;; :deregister-event-handler
@ -104,8 +104,8 @@
(fn [value]
(let [clear-event (partial clear-handlers events/kind)]
(if (sequential? value)
(doseq [event (if (sequential? value) value [value])]
(clear-event event))))))
(doseq [event value] (clear-event event))
(clear-event value)))))
;; :db
@ -118,5 +118,6 @@
(register
:db
(fn [value]
(reset! app-db value)))
(if-not (identical? @app-db value)
(reset! app-db value))))

View File

@ -1,7 +1,7 @@
(ns re-frame.interceptor
(:require
[re-frame.loggers :refer [console]]
[re-frame.interop :refer [ratom? empty-queue debug-enabled?]]))
[re-frame.interop :refer [empty-queue debug-enabled?]]))
(def mandatory-interceptor-keys #{:id :after :before})

View File

@ -211,7 +211,7 @@
eddting operation. Nice and efficient, but fiddly. A bug generator
approach.
So, instead, we create an `f` which recalcualtes warnings from scratch
So, instead, we create an `f` which recalculates warnings from scratch
every time there is ANY change. It will inspect all the todos, and
reset ALL FLAGS every time (overwriting what was there previously)
and fully recalculate the list of duplicates (displayed at the bottom?).

View File

@ -1,7 +1,10 @@
(ns re-frame.trace
"Tracing for re-frame.
Alpha quality, subject to change/break at any time."
#?(:cljs (:require-macros [net.cgrand.macrovich :as macros]
[re-frame.trace :refer [finish-trace with-trace merge-trace!]]))
(:require [re-frame.interop :as interop]
#?(:clj [net.cgrand.macrovich :as macros])
[re-frame.loggers :refer [console]]))
(def id (atom 0))
@ -40,37 +43,38 @@
:child-of (or child-of (:id *current-trace*))
:start (interop/now)})
#?(:clj (defmacro finish-trace [trace]
`(when (is-trace-enabled?)
(let [end# (interop/now)
duration# (- end# (:start ~trace))]
(doseq [[k# cb#] @trace-cbs]
(try (cb# [(assoc ~trace
(macros/deftime
(defmacro finish-trace [trace]
`(when (is-trace-enabled?)
(let [end# (interop/now)
duration# (- end# (:start ~trace))]
(doseq [[k# cb#] @trace-cbs]
(try (cb# [(assoc ~trace
:duration duration#
:end (interop/now))])
#?(:clj (catch Exception e#
(console :error "Error thrown from trace cb" k# "while storing" ~trace e#)))
#?(:cljs (catch :default e#
(console :error "Error thrown from trace cb" k# "while storing" ~trace e#)))))))))
#?(:clj (catch Exception e#
(console :error "Error thrown from trace cb" k# "while storing" ~trace e#)))
#?(:cljs (catch :default e#
(console :error "Error thrown from trace cb" k# "while storing" ~trace e#))))))))
#?(:clj (defmacro with-trace
"Create a trace inside the scope of the with-trace macro
(defmacro with-trace
"Create a trace inside the scope of the with-trace macro
Common keys for trace-opts
:op-type - what kind of operation is this? e.g. :sub/create, :render.
:operation - identifier for the operation, for an subscription it would be the subscription keyword
tags - a map of arbitrary kv pairs"
[{:keys [operation op-type tags child-of] :as trace-opts} & body]
`(if (is-trace-enabled?)
(binding [*current-trace* (start-trace ~trace-opts)]
(try ~@body
(finally (finish-trace *current-trace*))))
(do ~@body))))
[{:keys [operation op-type tags child-of] :as trace-opts} & body]
`(if (is-trace-enabled?)
(binding [*current-trace* (start-trace ~trace-opts)]
(try ~@body
(finally (finish-trace *current-trace*))))
(do ~@body)))
#?(:clj (defmacro merge-trace! [m]
;; Overwrite keys in tags, and all top level keys.
`(when (is-trace-enabled?)
(let [new-trace# (-> (update *current-trace* :tags merge (:tags ~m))
(merge (dissoc ~m :tags)))]
(set! *current-trace* new-trace#))
nil)))
(defmacro merge-trace! [m]
;; Overwrite keys in tags, and all top level keys.
`(when (is-trace-enabled?)
(let [new-trace# (-> (update *current-trace* :tags merge (:tags ~m))
(merge (dissoc ~m :tags)))]
(set! *current-trace* new-trace#))
nil)))