mirror of
https://github.com/status-im/re-frame.git
synced 2025-02-22 14:58:12 +00:00
Add more comments to todomvc
This commit is contained in:
parent
209470e761
commit
4bba231548
@ -14,10 +14,15 @@
|
||||
|
||||
;; -- Debugging aids ----------------------------------------------------------
|
||||
(devtools/install!) ;; we love https://github.com/binaryage/cljs-devtools
|
||||
(enable-console-print!) ;; so println writes to console.log
|
||||
(enable-console-print!) ;; so that println writes to `console.log`
|
||||
|
||||
;; -- Routes and History ------------------------------------------------------
|
||||
|
||||
;; Although we use the secretary library below, that's mostly a historical
|
||||
;; accident. You might also consider using:
|
||||
;; - https://github.com/DomKM/silk
|
||||
;; - https://github.com/juxt/bidi
|
||||
;; We don't have a strong opinion.
|
||||
;;
|
||||
(defroute "/" [] (dispatch [:set-showing :all]))
|
||||
(defroute "/:filter" [filter] (dispatch [:set-showing (keyword filter)]))
|
||||
|
||||
@ -29,10 +34,23 @@
|
||||
|
||||
|
||||
;; -- Entry Point -------------------------------------------------------------
|
||||
|
||||
;; Within ../../resources/public/index.html you'll see this code
|
||||
;; window.onload = function () {
|
||||
;; todomvc.core.main();
|
||||
;; }
|
||||
;; So this is the entry function that kicks off the app once the HTML is loaded.
|
||||
;;
|
||||
(defn ^:export main
|
||||
[]
|
||||
;; Put an initial value into app-db.
|
||||
;; The event handler for `:initialise-db` can be found in `events.cljs`
|
||||
;; Using the sync version of dispatch means that value is in
|
||||
;; place before we go onto the next step.
|
||||
(dispatch-sync [:initialise-db])
|
||||
(reagent/render [todomvc.views/todo-app]
|
||||
|
||||
;; Render the UI into the HTML's <div id="app" /> element
|
||||
;; The view function `todomvc.views/todo-app` is the
|
||||
;; root view for the entire UI.
|
||||
(reagent/render [todomvc.views/todo-app] ;;
|
||||
(.getElementById js/document "app")))
|
||||
|
||||
|
@ -38,7 +38,7 @@
|
||||
;;
|
||||
;; When the application first starts, this will be the value put in app-db
|
||||
;; Unless, or course, there are todos in the LocalStore (see further below)
|
||||
;; Look in core.cljs for "(dispatch-sync [:initialise-db])"
|
||||
;; Look in `core.cljs` for "(dispatch-sync [:initialise-db])"
|
||||
;;
|
||||
|
||||
(def default-value ;; what gets put into app-db by default.
|
||||
@ -50,16 +50,19 @@
|
||||
;;
|
||||
;; Part of the todomvc challenge is to store todos in LocalStorage, and
|
||||
;; on app startup, reload the todos from when the program was last run.
|
||||
;; But we are not to load the setting for the "showing" filter. Just the todos.
|
||||
;; But the challenge stipulates to NOT load the setting for the "showing"
|
||||
;; filter. Just the todos.
|
||||
;;
|
||||
|
||||
(def ls-key "todos-reframe") ;; localstore key
|
||||
|
||||
(def ls-key "todos-reframe") ;; localstore key
|
||||
(defn todos->local-store
|
||||
"Puts todos into localStorage"
|
||||
[todos]
|
||||
(.setItem js/localStorage ls-key (str todos))) ;; sorted-map writen as an EDN map
|
||||
(.setItem js/localStorage ls-key (str todos))) ;; sorted-map writen as an EDN map
|
||||
|
||||
|
||||
;; register a coeffect handler which will load a value from localstore
|
||||
;; To see it used look in events.clj at the event handler for `:initialise-db`
|
||||
(re-frame/reg-cofx
|
||||
:local-store-todos
|
||||
(fn [cofx _]
|
||||
|
@ -8,12 +8,9 @@
|
||||
|
||||
;; -- Interceptors --------------------------------------------------------------
|
||||
;;
|
||||
;; XXX Add URL for docs here
|
||||
;; XXX figure out first time spec error
|
||||
;; XXX seems to be a bug if you refresh the page, and other than "All" is selected.
|
||||
|
||||
(defn check-and-throw
|
||||
"throw an exception if db doesn't match the spec."
|
||||
"throw an exception if db doesn't match the spec"
|
||||
[a-spec db]
|
||||
(when-not (s/valid? a-spec db)
|
||||
(throw (ex-info (str "spec check failed: " (s/explain-str a-spec db)) {}))))
|
||||
@ -28,8 +25,10 @@
|
||||
;; we attach it to each event handler which could update todos
|
||||
(def ->local-store (after todos->local-store))
|
||||
|
||||
|
||||
;; the chain of interceptors we use for all handlers that manipulate todos
|
||||
;; Each event handler can have its own set of interceptors (middleware)
|
||||
;; But we use the same set of interceptors for all event habdlers related
|
||||
;; to manipulating todos.
|
||||
;; A chain of interceptors is a vector.
|
||||
(def todo-interceptors [check-spec-interceptor ;; ensure the spec is still valid
|
||||
(path :todos) ;; 1st param to handler will be the value from this path
|
||||
->local-store ;; write todos to localstore
|
||||
@ -49,22 +48,23 @@
|
||||
|
||||
;; -- Event Handlers ----------------------------------------------------------
|
||||
|
||||
;; XXX make localstore a coeffect interceptor
|
||||
|
||||
;; usage: (dispatch [:initialise-db])
|
||||
;; usage: (dispatch [:initialise-db])
|
||||
(reg-event-fx ;; on app startup, create initial state
|
||||
:initialise-db ;; event id being handled
|
||||
[(inject-cofx :local-store-todos)
|
||||
[(inject-cofx :local-store-todos) ;; obtain todos from localstore
|
||||
check-spec-interceptor] ;; after the event handler runs, check that app-db matches the spec
|
||||
(fn [{:keys [db local-store-todos]} _] ;; the handler being registered
|
||||
{:db (assoc default-value :todos local-store-todos)})) ;; all hail the new state
|
||||
|
||||
|
||||
;; usage: (dispatch [:set-showing :active])
|
||||
;; usage: (dispatch [:set-showing :active])
|
||||
(reg-event-db ;; this handler changes the todo filter
|
||||
:set-showing ;; event-id
|
||||
[check-spec-interceptor (path :showing) trim-v] ;; this collection of interceptors wrap the handler
|
||||
|
||||
;; this chain of two interceptors wrap the handler
|
||||
[check-spec-interceptor (path :showing) trim-v]
|
||||
|
||||
;; The event handler
|
||||
;; Because of the path interceptor above, the 1st parameter to
|
||||
;; the handler below won't be the entire 'db', and instead will
|
||||
;; be the value at a certain path within db, namely :showing.
|
||||
@ -74,11 +74,18 @@
|
||||
new-filter-kw)) ;; return new state for the path
|
||||
|
||||
|
||||
;; usage: (dispatch [:add-todo "Finish comments"])
|
||||
;; usage: (dispatch [:add-todo "Finish comments"])
|
||||
(reg-event-db ;; given the text, create a new todo
|
||||
:add-todo
|
||||
|
||||
;; The standard set of interceptors, defined above, which we
|
||||
;; apply to all todos-modifiing event handlers. Looks after
|
||||
;; writing todos to local store, etc.
|
||||
todo-interceptors
|
||||
(fn [todos [text]] ;; the "path" interceptor in `todo-interceptors` means 1st parameter is :todos
|
||||
|
||||
;; The event handler function.
|
||||
;; The "path" interceptor in `todo-interceptors` means 1st parameter is :todos
|
||||
(fn [todos [text]]
|
||||
(let [id (allocate-next-id todos)]
|
||||
(assoc todos id {:id id :title text :done false}))))
|
||||
|
||||
|
@ -2,18 +2,18 @@
|
||||
(:require [re-frame.core :refer [reg-sub subscribe]]))
|
||||
|
||||
;; -------------------------------------------------------------------------------------
|
||||
;; Layer 2 (see the infographic for meaning)
|
||||
;; Layer 2 (see the Subscriptions Infographic for meaning)
|
||||
;;
|
||||
(reg-sub
|
||||
:showing
|
||||
(fn [db _] ;; db is the (map) value in app-db
|
||||
(:showing db))) ;; I repeat: db is a value. Not a ratom. And this fn does not return a reaction, just a value.
|
||||
|
||||
;; so that `fn` is a pure function
|
||||
;; that `fn` is a pure function
|
||||
|
||||
;; Next, the registration of a similar handler is done in two steps.
|
||||
;; First, we `defn` a pure handler function. Then, we use `reg-sub` to register it.
|
||||
;; Two steps. This is different to the first registration, which was done in one step.
|
||||
;; Two steps. This is different to that first registration, above, which was done in one step.
|
||||
(defn sorted-todos
|
||||
[db _]
|
||||
(:todos db))
|
||||
@ -79,10 +79,14 @@
|
||||
;; - the second function (which is the computation, destructures this 2-vector as its first parameter)
|
||||
(reg-sub
|
||||
:visible-todos
|
||||
(fn [query-v _] ;; returns a vector of two signals.
|
||||
|
||||
;; signal function
|
||||
;; returns a vector of two input signals
|
||||
(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)
|
||||
@ -109,8 +113,6 @@
|
||||
;; Now for some syntactic sugar...
|
||||
;; The purpose of the sugar is to remove boilerplate noise. To distill to the essential
|
||||
;; in 90% of cases.
|
||||
;; Is this a good idea?
|
||||
;; If it is a good idea, is this good syntax?
|
||||
;; Because it is so common to nominate 1 or more input signals,
|
||||
;; reg-sub provides some macro sugar so you can nominate a very minimal
|
||||
;; vector of input signals. The 1st function is not needed.
|
||||
|
@ -49,42 +49,38 @@
|
||||
|
||||
(defn task-list
|
||||
[]
|
||||
(let [visible-todos (subscribe [:visible-todos])
|
||||
all-complete? (subscribe [:all-complete?])]
|
||||
(fn []
|
||||
(let [visible-todos @(subscribe [:visible-todos])
|
||||
all-complete? @(subscribe [:all-complete?])]
|
||||
[:section#main
|
||||
[:input#toggle-all
|
||||
{:type "checkbox"
|
||||
:checked @all-complete?
|
||||
:on-change #(dispatch [:complete-all-toggle (not @all-complete?)])}]
|
||||
:checked all-complete?
|
||||
:on-change #(dispatch [:complete-all-toggle (not all-complete?)])}]
|
||||
[:label
|
||||
{:for "toggle-all"}
|
||||
"Mark all as complete"]
|
||||
[:ul#todo-list
|
||||
(for [todo @visible-todos]
|
||||
^{:key (:id todo)} [todo-item todo])]])))
|
||||
(for [todo visible-todos]
|
||||
^{:key (:id todo)} [todo-item todo])]]))
|
||||
|
||||
|
||||
(defn footer-controls
|
||||
[]
|
||||
(let [footer-stats (subscribe [:footer-counts])
|
||||
showing (subscribe [:showing])]
|
||||
(fn []
|
||||
(let [[active done] @footer-stats
|
||||
a-fn (fn [filter-kw txt]
|
||||
[:a {:class (when (= filter-kw @showing) "selected")
|
||||
(let [[active done] @(subscribe [:footer-counts])
|
||||
showing @(subscribe [:showing])
|
||||
a-fn (fn [filter-kw txt]
|
||||
[:a {:class (when (= filter-kw showing) "selected")
|
||||
:href (str "#/" (name filter-kw))} txt])]
|
||||
[:footer#footer
|
||||
|
||||
[:span#todo-count
|
||||
[:strong active] " " (case active 1 "item" "items") " left"]
|
||||
[:ul#filters
|
||||
[:li (a-fn :all "All")]
|
||||
[:li (a-fn :active "Active")]
|
||||
[:li (a-fn :done "Completed")]]
|
||||
(when (pos? done)
|
||||
[:button#clear-completed {:on-click #(dispatch [:clear-completed])}
|
||||
"Clear completed"])]))))
|
||||
[:footer#footer
|
||||
[:span#todo-count
|
||||
[:strong active] " " (case active 1 "item" "items") " left"]
|
||||
[:ul#filters
|
||||
[:li (a-fn :all "All")]
|
||||
[:li (a-fn :active "Active")]
|
||||
[:li (a-fn :done "Completed")]]
|
||||
(when (pos? done)
|
||||
[:button#clear-completed {:on-click #(dispatch [:clear-completed])}
|
||||
"Clear completed"])]))
|
||||
|
||||
|
||||
(defn task-entry
|
||||
@ -99,13 +95,11 @@
|
||||
|
||||
(defn todo-app
|
||||
[]
|
||||
(let [todos (subscribe [:todos])]
|
||||
(fn []
|
||||
[:div
|
||||
[:section#todoapp
|
||||
[task-entry]
|
||||
(when (seq @todos)
|
||||
[task-list])
|
||||
[footer-controls]]
|
||||
[:footer#info
|
||||
[:p "Double-click to edit a todo"]]])))
|
||||
[:div
|
||||
[:section#todoapp
|
||||
[task-entry]
|
||||
(when (seq @(subscribe [:todos]))
|
||||
[task-list])
|
||||
[footer-controls]]
|
||||
[:footer#info
|
||||
[:p "Double-click to edit a todo"]]])
|
||||
|
Loading…
x
Reference in New Issue
Block a user