Merge branch 'master' into feature/simple-example
This commit is contained in:
commit
b3faf34f25
38
README.md
38
README.md
|
@ -8,32 +8,16 @@ y'know. Pretty good.
|
|||
|
||||
> -- Terry Pratchett, The Fifth Elephant
|
||||
|
||||
## Status
|
||||
|
||||
re-frame is still Alpha, not published to clojars [and its use may involve pain](https://github.com/Day8/re-frame/issues/4). But it is close to Beta.
|
||||
|
||||
Be sure you use v0.5.0 of reagent.
|
||||
|
||||
Todo:
|
||||
- provide a hook for logging and exception handling
|
||||
- implement pure event handlers. A macro will be needed.
|
||||
- continue to use Historian? Or roll our own undo. Only 20 lines of code.
|
||||
|
||||
<!--
|
||||
<img src="http://leiningen.org/img/leiningen.jpg"
|
||||
alt="Leiningen logo" title="The man himself" align="right" />
|
||||
-->
|
||||
|
||||
## Why Should You Care About re-frame?
|
||||
|
||||
Either:
|
||||
|
||||
1. You want to develop an app in ClojureScript, and you are looking for a framework; or
|
||||
2. You believe that by early 2015 ReactJS had effectively won the JavaScript framework wars and
|
||||
you are curious about the bigger implications. Could it be that the combination of
|
||||
2. You believe that, by early 2015, ReactJS had won the JavaScript framework wars and
|
||||
you are curious about the bigger implications. Could the combination of
|
||||
`reactive programming`, `functional programming` and `immutable data`
|
||||
**will completely change everything**? If so, what does that look like in a language
|
||||
that naturally embodies those paradigms?
|
||||
**completely change everything**? If so, what would that look like in a language
|
||||
that embraces those paradigms?
|
||||
|
||||
|
||||
## re-frame
|
||||
|
@ -631,7 +615,7 @@ So let's now look at how to write and register the subscription handler for `:cu
|
|||
(reaction (get-in @db [:path :to :a :map cid]))) ;; re-runs each time db changes
|
||||
|
||||
;; register our query handler
|
||||
(register
|
||||
(register-subs
|
||||
:customer-query ;; the id (the name of the query()
|
||||
customer-query) ;; the function which will perform the query
|
||||
```
|
||||
|
@ -691,7 +675,7 @@ Let's sketch out the situation described above ...
|
|||
The subscription-handler might be written:
|
||||
|
||||
```Clojure
|
||||
(register
|
||||
(register-subs
|
||||
:sorted-items ;; the query id (the name of the query)
|
||||
(fn [db [_]] ;; the handler for the subscription
|
||||
(reaction
|
||||
|
@ -747,7 +731,7 @@ Luckily, we can easily fix that up by tweaking our subscription function so
|
|||
that it chains `reactions`:
|
||||
|
||||
```Clojure
|
||||
(register
|
||||
(register-subs
|
||||
:sorted-items ;; the query id
|
||||
(fn [db [_]]
|
||||
(let [items (reaction (get-in @db [:some :path :to :items]))] ;; reaction #1
|
||||
|
@ -959,7 +943,7 @@ then to register those handlers with the router.
|
|||
Here's how we would register our event handler:
|
||||
|
||||
```Clojure
|
||||
(register
|
||||
(register-pure-handler
|
||||
:delete-item ;; the event id (name)
|
||||
handle-delete) ;; the handler function for that event
|
||||
```
|
||||
|
@ -1139,6 +1123,12 @@ To build an app using re-frame, you'll have to:
|
|||
- write and register event handler functions (control layer and/or state transition layer).
|
||||
|
||||
|
||||
### Licence
|
||||
|
||||
Copyright © 2015 Michael Thompson
|
||||
|
||||
Distributed under The MIT License (MIT) - See LICENSE.txt
|
||||
|
||||
[SPAs]:http://en.wikipedia.org/wiki/Single-page_application
|
||||
[Reagent]:http://reagent-project.github.io/
|
||||
[Dan Holmsand]:https://twitter.com/holmsand
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 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
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -1,7 +1,7 @@
|
|||
(defproject re-frame "0.1.6"
|
||||
:description "A reagent framework"
|
||||
(defproject re-frame "0.1.7"
|
||||
:description "A Reagent Framework For Writing SPAs, in Clojurescript"
|
||||
:url "https://github.com/Day8/re-frame.git"
|
||||
|
||||
:license {:name "MIT"}
|
||||
:dependencies [[org.clojure/clojure "1.6.0"]
|
||||
|
||||
[org.clojure/clojurescript "0.0-2760"]
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
(ns re-frame.core
|
||||
(:require
|
||||
[re-frame.handlers :as handlers]
|
||||
[re-frame.subs :as subs]
|
||||
[re-frame.middleware :as middleware]))
|
||||
|
||||
|
||||
;; -- API -------
|
||||
|
||||
(def register-handler handlers/register)
|
||||
(def dispatch handlers/dispatch)
|
||||
(def dispatch-sync handlers/dispatch-sync)
|
||||
|
||||
(def register-subs subs/register)
|
||||
(def subscribe subs/subscribe)
|
||||
|
||||
|
||||
|
||||
(def pure middleware/pure)
|
||||
(def undoable middleware/undoable)
|
||||
(def path middleware/path)
|
||||
(def validate middleware/validate)
|
||||
(def log-events middleware/log-events)
|
||||
(def apply-event middleware/apply-event)
|
||||
|
||||
|
||||
|
||||
;; -- Convienience API -------
|
||||
|
||||
;; virtually ever handler will be pure, make it easy
|
||||
(defn register-pure-handler
|
||||
([id handler]
|
||||
(register-handler id pure handler))
|
||||
([id middleware handler]
|
||||
(register-handler id (comp pure middleware) handler)))
|
|
@ -22,16 +22,6 @@
|
|||
(register event-id (middleware handler-fn))))
|
||||
|
||||
|
||||
(defn register
|
||||
"register a handler for an event"
|
||||
([event-id handler-fn]
|
||||
(when (contains? @id->fn event-id)
|
||||
(warn "re-frame: overwriting an event-handler for: " event-id)) ;; allow it, but warn.
|
||||
(swap! id->fn assoc event-id handler-fn))
|
||||
|
||||
([event-id middleware handler-fn]
|
||||
(register event-id (middleware handler-fn))))
|
||||
|
||||
;; -- The Event Conveyor Belt --------------------------------------------------------------------
|
||||
;;
|
||||
;; Moves events from "dispatch" to the router loop.
|
||||
|
|
|
@ -1,88 +1,102 @@
|
|||
(ns re-frame.middleware
|
||||
(:require
|
||||
[reagent.ratom :refer [IReactiveAtom]]
|
||||
[re-frame.undo :refer [store-now!]]))
|
||||
[reagent.ratom :refer [IReactiveAtom]]
|
||||
[re-frame.undo :refer [store-now!]]
|
||||
[re-frame.utils :refer [warn]]))
|
||||
|
||||
;; -- Middleware Factories -------------------------------------------------------------------------
|
||||
;;
|
||||
;; Middleware wraps handlers, providing a composable pipeline. We use middleware so the handlers
|
||||
;; themselves are kept as simple as possible. In particualr, the handlers can be kept as pure functions.
|
||||
;;
|
||||
;; My attempt to explain, by skirting around the hard bits is as follows ...
|
||||
;;
|
||||
;; Use "comp" to compose middelware, like this:
|
||||
;;
|
||||
;; (def midware (comp undoable pure (validate some-fn))) ;; midware is a function
|
||||
;;
|
||||
;; then imagine that we have a pure handler:
|
||||
;;
|
||||
;; (defn my-handler
|
||||
;; [db v]
|
||||
;; (assoc db :some-key 42))
|
||||
;;
|
||||
;; then apply the composed middleare to my-handler:
|
||||
;;
|
||||
;; (def h (midware my-handler)) ;; h is "my-handler" wrapped in middleware
|
||||
;;
|
||||
;; I could call h like this:
|
||||
;; (h app-db [:some-key 23]) <---- h is a handler, just pass in 'db' and 'v'
|
||||
;;
|
||||
;; Which means, you could just register 'h'
|
||||
;;
|
||||
;; (register
|
||||
;; :some-id
|
||||
;; h)
|
||||
;;
|
||||
;; Middleware factories do your head in initially, because they involve a function, returning a function,
|
||||
;; returning a function. So I'd suggest you might want to read this explanation
|
||||
;; (go to "Handlers and Middleware"):
|
||||
;; http://www.flyingmachinestudios.com/programming/boot-clj/
|
||||
;;
|
||||
|
||||
(defn undoable
|
||||
"Middleware which stores an undo checkpoint"
|
||||
[next-handler]
|
||||
(fn handler
|
||||
[app-db event-vec]
|
||||
(store-now!)
|
||||
(next-handler app-db event-vec)))
|
||||
;; Read this: https://github.com/Day8/re-frame/wiki/Middleware
|
||||
|
||||
|
||||
(defn pure
|
||||
"Middleware which allows you to write a pure handler.
|
||||
1. on the way through it extracts the value in the atom
|
||||
2. resets the atom with the returned value after calling the handler"
|
||||
[next-handler]
|
||||
(fn handler
|
||||
"Middleware which adapts a pure handler to the non-pure standard calling convention"
|
||||
[handler]
|
||||
(fn new-handler
|
||||
[app-db event-vec]
|
||||
(assert (satisfies? IReactiveAtom app-db) "re-frame: make-pure not given a Ratom")
|
||||
(reset! app-db (next-handler @app-db event-vec))))
|
||||
(assert (satisfies? IReactiveAtom app-db)
|
||||
(str "re-frame: pure not given a Ratom" (if (map? app-db) ". Perhaps \"pure\" is used twice.")))
|
||||
(reset! app-db (handler @app-db event-vec))))
|
||||
|
||||
|
||||
;; example of applying
|
||||
|
||||
;; warning: untested
|
||||
(defn undoable
|
||||
"Middleware which stores an undo checkpoint, prior to handler being called."
|
||||
[handler]
|
||||
(fn new-handler
|
||||
[app-db event-vec]
|
||||
(store-now!)
|
||||
(handler app-db event-vec)))
|
||||
|
||||
|
||||
|
||||
;; warning: untested
|
||||
(defn apply-event
|
||||
"Middleware which removes the first bit of v, and \"expands\" other parameters.
|
||||
Normally handlers get two paramters: db and v.
|
||||
With this middleware, if v was [:id 1 2], the handler would be called with db, 1, 2.
|
||||
Use the middleware in the very last place -- right-most in comp"
|
||||
[handler]
|
||||
(fn new-handler
|
||||
[db v]
|
||||
(apply handler (cons db (rest v)))))
|
||||
|
||||
|
||||
;; warning: untested
|
||||
(defn path
|
||||
"Supplies a sub-tree of `app-db` to the handler.
|
||||
Assumes pure is in the middleware pipeline prior.
|
||||
Grafts the result back into app-db."
|
||||
[p]
|
||||
(fn middleware
|
||||
[handler]
|
||||
(fn new-handler
|
||||
[db v]
|
||||
(if (satisfies? IReactiveAtom db)
|
||||
(str "re-frame: \"path\" used in middleware, without prior \"pure\"."))
|
||||
(if-not (vector? p)
|
||||
(warn "re-frame: \"path\" expected a vector, got: " v))
|
||||
(assoc-in db p (handler (get-in db p) v)))))
|
||||
|
||||
|
||||
;; warning: untested
|
||||
(defn validate
|
||||
"Middleware that applies a validation function to the db after the handler is finished.
|
||||
The validation function f, might assoc warnings and errors to the new state, created by the handler.
|
||||
By validation, I mean validation of what the user has entered, or the state they have taken the app too"
|
||||
[f]
|
||||
(fn middleware
|
||||
[handler]
|
||||
(fn new-handler
|
||||
[db v]
|
||||
(f (handler db v)))))
|
||||
|
||||
|
||||
;; warning: untested
|
||||
(defn log-events
|
||||
"Middleware that logs events (vec) using to the given logger fucntion"
|
||||
[logger]
|
||||
(fn middleware
|
||||
[handler]
|
||||
(fn new-handler
|
||||
[db v]
|
||||
(logger v)
|
||||
(handler db v))))
|
||||
|
||||
|
||||
|
||||
|
||||
;; warning: untested
|
||||
;; check the state of db AFTER the handler has run, using a prismatic Schema.
|
||||
#_(defn check-schema
|
||||
"Middleware for checking that a handlers mutations leave the state in a schema-matching way"
|
||||
[a-prismatic-schema]
|
||||
(fn middlewear
|
||||
(fn middleware
|
||||
[next-handler]
|
||||
(fn handler
|
||||
[db v]
|
||||
(let [val (next-handler db v)
|
||||
valid? true] ;; XXXXX replace true by code which checks the schema using original parameter
|
||||
(if (not valid?)
|
||||
(warn "re-frame: schema not valid after:" ))
|
||||
val))))
|
||||
|
||||
|
||||
(defn validate
|
||||
"Middleware that applies a validation function to the db after the handler is finished.
|
||||
The validation function f, might assoc warnings and errors to the new state, created by the handler.
|
||||
By validation, I mean validation of what the user has entered, or the state they have taken the app too"
|
||||
[f]
|
||||
(fn middlewear
|
||||
[next-handler]
|
||||
(fn handler
|
||||
[db v]
|
||||
(f (next-handler db v)))))
|
||||
|
||||
|
||||
(warn "re-frame: schema not valid after:" v))
|
||||
val))))
|
Loading…
Reference in New Issue