merge
This commit is contained in:
commit
2551c6fcb4
|
@ -0,0 +1,71 @@
|
||||||
|
|
||||||
|
|
||||||
|
## Planned for v0.3.0
|
||||||
|
|
||||||
|
- automatically wrap subscriptions in a `reaction` (removing the need for over
|
||||||
|
10 keystrokes per handler!!). Just kidding there are better reasons than that.
|
||||||
|
- produce a more fully featured todomvc (beyond the standard one), todomvc-with-extras
|
||||||
|
- use `enrich` to handle todo duplication
|
||||||
|
- show testing
|
||||||
|
- show debug
|
||||||
|
- begin to use goog.Logger ?? How to let client apps know about exceptions, etc ??
|
||||||
|
|
||||||
|
|
||||||
|
## v0.2.0 (2015-03-06)
|
||||||
|
|
||||||
|
Being blocked by:
|
||||||
|
|
||||||
|
In todomvc, to be done tomorrow ahead of release:
|
||||||
|
- fix bug in todomvc which means the footer disappears and doesn't come back.
|
||||||
|
- add localstorage
|
||||||
|
- add history, back button etc.
|
||||||
|
|
||||||
|
### Breaking
|
||||||
|
|
||||||
|
Renames:
|
||||||
|
- `register-pure-handler` renamed to `register-handler` (and existing low level `register-handler` becomes `register-handler-base` but is not a part of the API).
|
||||||
|
- remove `apply-event` middleware and replace with similar `trim-v`
|
||||||
|
- rename `register-subs` to `register-sub` (avoid confusion over possible plurals)
|
||||||
|
- rename `set-max-undos` to `set-max-undos!`
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- `undoable` middleware is now a factory. Where before you used this `undoable`,
|
||||||
|
you must now use this `(undoable "some explanation")`. See further below.
|
||||||
|
|
||||||
|
### Headline
|
||||||
|
|
||||||
|
- exceptions in handler now reported more sanely via console.error.
|
||||||
|
(core.async really messes with a good stack)
|
||||||
|
- example `todomvc` available in examples folder.
|
||||||
|
- Wiki documentation is now more substantial.
|
||||||
|
- introduce new handler middleware: `debug`, `enrich` and `after`
|
||||||
|
|
||||||
|
### Other:
|
||||||
|
- exceptions in a go loop are a special type of hell. Improve the reporting of exceptions happening in event handlers.
|
||||||
|
- allow Middleware to be registered as a vector. data > functions > macros
|
||||||
|
- fix two bugs in undo implementation
|
||||||
|
- name licence file correctly, thanks to @smith
|
||||||
|
- fix typo in readme, thanks to @btheado
|
||||||
|
- Readme now admits to 200 lines of code, not 100.
|
||||||
|
- dispatch now explicitly returns nil
|
||||||
|
- travis integration (not that we have any tests currently)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Details On Undo Changes
|
||||||
|
|
||||||
|
The undo/redo feature built into re-frame is now more functional
|
||||||
|
(at the cost of a breaking change).
|
||||||
|
|
||||||
|
There is now an explanation associated with each undo state describing
|
||||||
|
modification. This allows an app to inform the user what actions
|
||||||
|
they will be undoing or redoing.
|
||||||
|
|
||||||
|
Previously `undoable` was simply middleware, but it is now a middleware factory.
|
||||||
|
|
||||||
|
Essentially, that means you can't use it "plain" anymore, and instead you must
|
||||||
|
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.
|
|
@ -34,15 +34,15 @@
|
||||||
check-schema ;; middleware
|
check-schema ;; middleware
|
||||||
(fn [_ _] ;; the handler
|
(fn [_ _] ;; the handler
|
||||||
(merge default-value
|
(merge default-value
|
||||||
(get-local-storage)))) ;; all hail the new state
|
#_(get-local-storage)))) ;; all hail the new state
|
||||||
|
|
||||||
|
|
||||||
(register-handler ;; handlers changes the footer filter
|
(register-handler ;; handlers changes the footer filter
|
||||||
:set-showing ;; event-id
|
:set-showing ;; event-id
|
||||||
[check-schema debug trim-v] ;; middleware (wraps the handler)
|
[(path [:showing]) #_write-ls check-schema debug trim-v] ;; middleware (wraps the handler)
|
||||||
(fn ;; handler
|
(fn ;; handler
|
||||||
[db [filter-kw]]
|
[db [filter-kw]]
|
||||||
(assoc db :showing filter-kw)))
|
filter-kw))
|
||||||
|
|
||||||
|
|
||||||
(register-handler ;; given the text, create a new todo
|
(register-handler ;; given the text, create a new todo
|
||||||
|
|
|
@ -65,5 +65,13 @@
|
||||||
handler-fn (lookup-handler event-id)]
|
handler-fn (lookup-handler event-id)]
|
||||||
(if (nil? handler-fn)
|
(if (nil? handler-fn)
|
||||||
(warn "re-frame: no event handler registered for: \"" event-id "\". Ignoring.") ;; TODO: make exception
|
(warn "re-frame: no event handler registered for: \"" event-id "\". Ignoring.") ;; TODO: make exception
|
||||||
(handler-fn app-db event-v))))
|
(try
|
||||||
|
(handler-fn app-db event-v)
|
||||||
|
(catch :default e
|
||||||
|
(do
|
||||||
|
;; use of a core.async loop seems to completely ruin exception stacks.
|
||||||
|
;; So we're going print the exception to the console here, before it gets trashed.
|
||||||
|
(.error js/console e)
|
||||||
|
(throw e)))))))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -11,24 +11,29 @@
|
||||||
|
|
||||||
(defn pure
|
(defn pure
|
||||||
"Acts as an adaptor, allowing handlers to be writen as pure functions.
|
"Acts as an adaptor, allowing handlers to be writen as pure functions.
|
||||||
The re-frame router will pass in an atom as the first parameter. This middleware
|
The re-frame router will pass in an atom as the first parameter. This
|
||||||
adapts that to the value within the atom.
|
middleware adapts that atom to be the value within the atom.
|
||||||
If you strip away the error/efficiency checks, this middleware is just:
|
If you strip away the error/efficiency checks, this middleware is just doing:
|
||||||
(reset! app-db (handler @app-db event-vec))"
|
(reset! app-db (handler @app-db event-vec))
|
||||||
|
You don't have to use this middleare directly. Is supplied by default
|
||||||
|
when you use register-handler.
|
||||||
|
The only way to by-pass use of pure is to use the low level registration function
|
||||||
|
re-frame.handlers/register-handler-base"
|
||||||
[handler]
|
[handler]
|
||||||
(fn pure-handler
|
(fn pure-handler
|
||||||
[app-db event-vec]
|
[app-db event-vec]
|
||||||
(assert (satisfies? IReactiveAtom app-db)
|
(if (not (satisfies? IReactiveAtom app-db))
|
||||||
(str "re-frame: pure not given a Ratom."
|
(do
|
||||||
(if (map? app-db)
|
(if (map? app-db)
|
||||||
" Looks like \"pure\" is in the middleware pipeline twice."
|
(warn "re-frame: Looks like \"pure\" is in the middleware pipeline twice. Ignoring.")
|
||||||
(str " Got: " app-db))))
|
(warn "re-frame: \"pure\" middleware not given a Ratom. Got: " app-db))
|
||||||
(let [orig-db @app-db
|
handler) ;; turn this into a noop handler
|
||||||
new-db (handler orig-db event-vec)]
|
(let [orig-db @app-db
|
||||||
(if (nil? new-db)
|
new-db (handler orig-db event-vec)]
|
||||||
(warn "re-frame: your pure handler returned nil. It should return the new db.")
|
(if (nil? new-db)
|
||||||
(if-not (identical? orig-db new-db)
|
(warn "re-frame: your pure handler returned nil. It should return the new db state.")
|
||||||
(reset! app-db new-db))))))
|
(if-not (identical? orig-db new-db)
|
||||||
|
(reset! app-db new-db)))))))
|
||||||
|
|
||||||
|
|
||||||
(defn debug
|
(defn debug
|
||||||
|
@ -38,25 +43,15 @@
|
||||||
[handler]
|
[handler]
|
||||||
(fn debug-handler
|
(fn debug-handler
|
||||||
[db v]
|
[db v]
|
||||||
(if (satisfies? IReactiveAtom db)
|
|
||||||
(str "re-frame: \"debug\" middleware used without prior \"pure\"."))
|
|
||||||
(group "re-frame event: " v)
|
(group "re-frame event: " v)
|
||||||
(let [new-db (handler db v)
|
(let [new-db (handler db v)
|
||||||
diff (data/diff db new-db)]
|
diff (data/diff db new-db)]
|
||||||
(log "only before: " (first diff))
|
(log "only before: " (first diff))
|
||||||
(log " only after: " (second diff))
|
(log "only after : " (second diff))
|
||||||
(groupEnd)
|
(groupEnd)
|
||||||
new-db)))
|
new-db)))
|
||||||
|
|
||||||
|
|
||||||
(defn undoable
|
|
||||||
"Middleware which stores an undo checkpoint."
|
|
||||||
[handler]
|
|
||||||
(fn undoable-handler
|
|
||||||
[app-db event-vec]
|
|
||||||
(store-now!)
|
|
||||||
(handler app-db event-vec)))
|
|
||||||
|
|
||||||
|
|
||||||
(defn trim-v
|
(defn trim-v
|
||||||
"Middleware which removes the first element of v which allows you to write
|
"Middleware which removes the first element of v which allows you to write
|
||||||
|
@ -73,27 +68,46 @@
|
||||||
|
|
||||||
|
|
||||||
(defn path
|
(defn path
|
||||||
"Supplies a sub-tree of `db` to the handler. A narrowed view.
|
"A middleware factory which supplies a sub-tree of `db` to the handler.
|
||||||
|
Supplies a narrowed view.
|
||||||
Assumes \"pure\" is in the middleware pipeline prior.
|
Assumes \"pure\" is in the middleware pipeline prior.
|
||||||
Grafts the result back into db.
|
Grafts the result back into db.
|
||||||
If a get-in of the path results in a nil, then \"default-fn\" will be called to supply a value.
|
If a get-in of the path results in a nil, then \"default-fn\" will be called to supply a value.
|
||||||
XXX very like update-in. Should the name be more indicative of that closeness? "
|
XXX very like update-in. Should the name be more indicative of that closeness? "
|
||||||
([p]
|
([p]
|
||||||
(path p hash-map))
|
(path p nil))
|
||||||
([p default-fn]
|
([p default-fn]
|
||||||
(fn middleware
|
(fn middleware
|
||||||
[handler]
|
[handler]
|
||||||
(fn path-handler
|
(fn path-handler
|
||||||
[db v]
|
[db v]
|
||||||
(if (satisfies? IReactiveAtom db)
|
|
||||||
(str "re-frame: \"path\" used in middleware, without prior \"pure\"."))
|
|
||||||
(if-not (vector? p)
|
(if-not (vector? p)
|
||||||
(warn "re-frame: \"path\" expected a vector, got: " p))
|
(warn "re-frame: \"path\" expected a vector, got: " p))
|
||||||
(let [val (get-in db p)
|
(let [val (get-in db p)
|
||||||
val (if (nil? val) (default-fn) val)]
|
val (if (and (nil? val) (fn? default-fn)) (default-fn) val)]
|
||||||
(assoc-in db p (handler val v)))))))
|
(assoc-in db p (handler val v)))))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn undoable
|
||||||
|
"A Middleware factory which stores an undo checkpoint.
|
||||||
|
\"explanation\" can be either a string or a function. If it is a
|
||||||
|
function then it must be: (db event-vec) -> string.
|
||||||
|
\"explanation\" can be nil. in which case no
|
||||||
|
"
|
||||||
|
[explanation]
|
||||||
|
(fn middleware
|
||||||
|
[handler]
|
||||||
|
(fn undoable-handler
|
||||||
|
[db event-vec]
|
||||||
|
(let [explanation (cond
|
||||||
|
(fn? explanation) (explanation db event-vec)
|
||||||
|
(string? explanation) explanation
|
||||||
|
(nil? explanation) ""
|
||||||
|
:else (warn "re-frame: undoable given bad parameter. Got: " explanation))]
|
||||||
|
(store-now! explanation)
|
||||||
|
(handler db event-vec)))))
|
||||||
|
|
||||||
|
|
||||||
(defn enrich
|
(defn enrich
|
||||||
"Middleware factory which runs a given function \"f\" in the after position.
|
"Middleware factory which runs a given function \"f\" in the after position.
|
||||||
\"f\" is (db) -> db
|
\"f\" is (db) -> db
|
||||||
|
@ -126,7 +140,8 @@
|
||||||
position presumably for side effects.
|
position presumably for side effects.
|
||||||
\"f\" is given the value of \"db\". It's return value is ignored.
|
\"f\" is given the value of \"db\". It's return value is ignored.
|
||||||
Examples: \"f\" can run schema validation. Or write current state to localstorage. etc.
|
Examples: \"f\" can run schema validation. Or write current state to localstorage. etc.
|
||||||
In effect, \"f\" is meant to sideeffect. It gets no chance to change db. See \"derive\" (if you need that.)"
|
In effect, \"f\" is meant to sideeffect. It gets no chance to change db. See \"enrich\"
|
||||||
|
(if you need that.)"
|
||||||
[f]
|
[f]
|
||||||
(fn middleware
|
(fn middleware
|
||||||
[handler]
|
[handler]
|
||||||
|
|
|
@ -32,10 +32,7 @@
|
||||||
_ (if (:flush-dom (meta event-v)) ;; check the event for metadata
|
_ (if (:flush-dom (meta event-v)) ;; check the event for metadata
|
||||||
(do (flush) (<! (timeout 20))) ;; wait just over one annimation frame (16ms), to rensure all pending GUI work is flushed to the DOM.
|
(do (flush) (<! (timeout 20))) ;; wait just over one annimation frame (16ms), to rensure all pending GUI work is flushed to the DOM.
|
||||||
(<! (timeout 0)))] ;; just in case we are handling one dispatch after an other, give the browser back control to do its stuff
|
(<! (timeout 0)))] ;; just in case we are handling one dispatch after an other, give the browser back control to do its stuff
|
||||||
(try
|
(handle event-v)
|
||||||
(handle event-v) ;; XXX There should be a plugable place to write exceptions and warns
|
|
||||||
(catch js/Object e
|
|
||||||
(.error js/console e))) ;; exceptions are a special kind of rubbish when you are in a goloop. So catch it here and print.
|
|
||||||
(recur)))
|
(recur)))
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,8 +52,13 @@
|
||||||
|
|
||||||
|
|
||||||
(defn dispatch-sync
|
(defn dispatch-sync
|
||||||
"Invoke the event handler sycronously, avoiding the async-inducing use of core.async/chan"
|
"Send an event to be processed by the registered handler, but avoid the async-inducing
|
||||||
|
use of core.async/chan.
|
||||||
|
|
||||||
|
Usage example:
|
||||||
|
(dispatch-sync [:delete-item 42])"
|
||||||
[event-v]
|
[event-v]
|
||||||
(handle event-v))
|
(handle event-v)
|
||||||
|
nil) ;; Ensure nil return. See https://github.com/Day8/re-frame/wiki/Returning-False
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,38 +1,56 @@
|
||||||
(ns re-frame.undo
|
(ns re-frame.undo
|
||||||
(:require-macros [reagent.ratom :refer [reaction]])
|
(:require-macros [reagent.ratom :refer [reaction]])
|
||||||
(:require
|
(:require
|
||||||
|
[re-frame.utils :refer [warn]]
|
||||||
[reagent.core :as reagent]
|
[reagent.core :as reagent]
|
||||||
[re-frame.db :refer [app-db]]
|
[re-frame.db :refer [app-db]]
|
||||||
[re-frame.handlers :as handlers]
|
[re-frame.handlers :as handlers]
|
||||||
[re-frame.subs :as subs ]))
|
[re-frame.subs :as subs]))
|
||||||
|
|
||||||
|
|
||||||
;; -- History -------------------------------------------------------------------------------------
|
;; -- History -------------------------------------------------------------------------------------
|
||||||
;;
|
;;
|
||||||
;;
|
;;
|
||||||
(def ^:private max-undos (atom 50)) ;; maximum number of undo states maintained
|
(def ^:private max-undos (atom 50)) ;; maximum number of undo states maintained
|
||||||
(defn set-max-undos
|
(defn set-max-undos!
|
||||||
[n]
|
[n]
|
||||||
(reset! max-undos n))
|
(reset! max-undos n))
|
||||||
|
|
||||||
;;
|
|
||||||
(def ^:private undo-list (reagent/atom [])) ;; a list of history states
|
(def ^:private undo-list (reagent/atom [])) ;; a list of history states
|
||||||
(def ^:private redo-list (reagent/atom [])) ;; a list of future states, caused by undoing
|
(def ^:private redo-list (reagent/atom [])) ;; a list of future states, caused by undoing
|
||||||
|
|
||||||
|
;; -- Explainations -----------------------------------------------------------
|
||||||
|
;;
|
||||||
|
;; Each undo has an associated explanation which can be displayed to the user.
|
||||||
|
;;
|
||||||
|
;; Seems really ugly to have mirrored vectors, but ...
|
||||||
|
;; the code kinda falls out when you do. I'm feeling lazy.
|
||||||
|
(def ^:private app-explain (reagent/atom "")) ;; mirrors app-db
|
||||||
|
(def ^:private undo-explain-list (reagent/atom [])) ;; mirrors undo-liat
|
||||||
|
(def ^:private redo-explain-list (reagent/atom [])) ;; mirrors redo-list
|
||||||
|
|
||||||
|
|
||||||
(defn clear-history!
|
(defn clear-history!
|
||||||
[]
|
[]
|
||||||
(reset! undo-list [])
|
(reset! undo-list [])
|
||||||
(reset! redo-list []))
|
(reset! redo-list [])
|
||||||
|
(reset! undo-explain-list [])
|
||||||
|
(reset! redo-explain-list []))
|
||||||
|
|
||||||
|
|
||||||
(defn store-now!
|
(defn store-now!
|
||||||
"stores the value currently in app-db, so the user can later undo"
|
"stores the value currently in app-db, so the user can later undo"
|
||||||
[]
|
[explanation]
|
||||||
(reset! redo-list []) ;; clear and redo state created by previous undos
|
(reset! redo-list []) ;; clear and redo state created by previous undos
|
||||||
|
(reset! redo-explain-list []) ;; clear and redo state created by previous undos
|
||||||
(reset! undo-list (vec (take
|
(reset! undo-list (vec (take
|
||||||
@max-undos
|
@max-undos
|
||||||
(conj @undo-list @app-db)))))
|
(conj @undo-list @app-db))))
|
||||||
|
(reset! undo-explain-list (vec (take
|
||||||
|
@max-undos
|
||||||
|
(conj @undo-explain-list @app-explain))))
|
||||||
|
(reset! app-explain explanation))
|
||||||
|
|
||||||
(defn undos?
|
(defn undos?
|
||||||
[]
|
[]
|
||||||
|
@ -42,7 +60,6 @@
|
||||||
[]
|
[]
|
||||||
(pos? (count @redo-list)))
|
(pos? (count @redo-list)))
|
||||||
|
|
||||||
|
|
||||||
;; -- subscriptions -----------------------------------------------------------------------------
|
;; -- subscriptions -----------------------------------------------------------------------------
|
||||||
|
|
||||||
(subs/register
|
(subs/register
|
||||||
|
@ -60,23 +77,60 @@
|
||||||
(reaction (redos?))))
|
(reaction (redos?))))
|
||||||
|
|
||||||
|
|
||||||
|
(subs/register
|
||||||
|
:undo-explantions
|
||||||
|
(fn handler
|
||||||
|
; "returns a vector of string explnations
|
||||||
|
[_ _]
|
||||||
|
(reaction (deref undo-explain-list))))
|
||||||
|
|
||||||
|
(subs/register
|
||||||
|
:redo-explantions
|
||||||
|
(fn handler
|
||||||
|
; "returns a vector of string explnations
|
||||||
|
[_ _]
|
||||||
|
(reaction (deref redo-explain-list))))
|
||||||
|
|
||||||
;; -- event handlers ----------------------------------------------------------------------------
|
;; -- event handlers ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
(defn undo
|
||||||
|
[undos cur redos]
|
||||||
|
(let [u @undos
|
||||||
|
r (cons @cur @redos)]
|
||||||
|
(reset! cur (last u))
|
||||||
|
(reset! redos r)
|
||||||
|
(reset! undos (pop u))))
|
||||||
|
|
||||||
|
|
||||||
(handlers/register-base ;; not a pure handler
|
(handlers/register-base ;; not a pure handler
|
||||||
:undo ;; usage: (dispatch [:undo])
|
:undo ;; usage: (dispatch [:undo])
|
||||||
(fn handler
|
(fn handler
|
||||||
[_ _]
|
[_ _]
|
||||||
(when (undos?)
|
(if-not (undos?)
|
||||||
(reset! redo-list (cons @app-db @redo-list))
|
(warn "re-frame: you did a (dispatch [:undo]), but there is nothing to undo.")
|
||||||
(reset! app-db (last @undo-list))
|
(do
|
||||||
(reset! undo-list (pop @undo-list)))))
|
(undo undo-list app-db redo-list)
|
||||||
|
(undo undo-explain-list app-explain redo-explain-list)))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn- redo
|
||||||
|
[undos cur redos]
|
||||||
|
(let [u (conj @undos @cur)
|
||||||
|
r @redos]
|
||||||
|
(reset! cur (first r))
|
||||||
|
(reset! redos (rest r))
|
||||||
|
(reset! undos u)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(handlers/register-base ;; not a pure handler
|
(handlers/register-base ;; not a pure handler
|
||||||
:redo ;; usage: (dispatch [:redo])
|
:redo ;; usage: (dispatch [:redo])
|
||||||
(fn handler
|
(fn handler
|
||||||
[_ _]
|
[_ _]
|
||||||
(when (redos?)
|
(if-not (redos?)
|
||||||
(reset! app-db (first @redo-list))
|
(warn "re-frame: you did a (dispatch [:redo]), but there is nothing to redo.")
|
||||||
(reset! redo-list (rest @redo-list))
|
(do
|
||||||
(reset! undo-list (conj @undo-list @app-db)))))
|
(redo undo-list app-db redo-list)
|
||||||
|
(redo undo-explain-list app-explain redo-explain-list)))))
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue