From 4bba2315487dab7fa9ad57c98d4bdc445f54d9fc Mon Sep 17 00:00:00 2001 From: Mike Thompson Date: Wed, 28 Dec 2016 22:42:45 +1100 Subject: [PATCH] Add more comments to todomvc --- examples/todomvc/src/todomvc/core.cljs | 26 ++++++++-- examples/todomvc/src/todomvc/db.cljs | 13 +++-- examples/todomvc/src/todomvc/events.cljs | 35 +++++++------ examples/todomvc/src/todomvc/subs.cljs | 14 +++--- examples/todomvc/src/todomvc/views.cljs | 62 +++++++++++------------- 5 files changed, 87 insertions(+), 63 deletions(-) diff --git a/examples/todomvc/src/todomvc/core.cljs b/examples/todomvc/src/todomvc/core.cljs index effcf37..dbc9d43 100644 --- a/examples/todomvc/src/todomvc/core.cljs +++ b/examples/todomvc/src/todomvc/core.cljs @@ -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
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"))) diff --git a/examples/todomvc/src/todomvc/db.cljs b/examples/todomvc/src/todomvc/db.cljs index 2473319..e7bdb72 100644 --- a/examples/todomvc/src/todomvc/db.cljs +++ b/examples/todomvc/src/todomvc/db.cljs @@ -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 _] diff --git a/examples/todomvc/src/todomvc/events.cljs b/examples/todomvc/src/todomvc/events.cljs index 3d32be9..83a7bcd 100644 --- a/examples/todomvc/src/todomvc/events.cljs +++ b/examples/todomvc/src/todomvc/events.cljs @@ -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})))) diff --git a/examples/todomvc/src/todomvc/subs.cljs b/examples/todomvc/src/todomvc/subs.cljs index d2c3453..ba0c55c 100644 --- a/examples/todomvc/src/todomvc/subs.cljs +++ b/examples/todomvc/src/todomvc/subs.cljs @@ -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. diff --git a/examples/todomvc/src/todomvc/views.cljs b/examples/todomvc/src/todomvc/views.cljs index 35e745a..5388041 100644 --- a/examples/todomvc/src/todomvc/views.cljs +++ b/examples/todomvc/src/todomvc/views.cljs @@ -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"]]])