Update local state directly with om cursors

This commit is contained in:
Nicholas Kariniemi 2014-08-09 18:18:27 +03:00
parent 5230dc9601
commit 11597a9b63
6 changed files with 106 additions and 131 deletions

View file

@ -66,13 +66,11 @@
(assoc state :recipes (dissoc (:recipes state) (:id event)))) (assoc state :recipes (dissoc (:recipes state) (:id event))))
(defn update-state-and-render [remote] (defn update-state-and-render [remote]
(let [out (chan) (view/render-app app-state)
view-events (view/render-app app-state)] (let [out (chan)]
(go-loop [] (go-loop []
(let [[event ch] (alts! [remote view-events]) (let [event (<! remote)
new-state (handle-event event @app-state)] new-state (handle-event event @app-state)]
(reset! app-state new-state) (reset! app-state new-state)
(when (= ch view-events)
(>! out event))
(recur))) (recur)))
out)) out))

View file

@ -26,29 +26,12 @@
(dom/on-window-scroll #(put! >events {:type :body-scroll :event %})))))) (dom/on-window-scroll #(put! >events {:type :body-scroll :event %}))))))
(defn render-app [state] (defn render-app [state]
(let [grub-add (chan) (let [>events (chan)
grub-update (chan) <events (a/pub >events :type)
grub-clear-all (chan) add-grubs-ch (chan)]
grub-remove (chan)
recipe-add (chan)
recipe-add-grubs (chan)
recipe-update (chan)
recipe-remove (chan)
out (a/merge [grub-add grub-update grub-clear-all grub-remove
recipe-add recipe-add-grubs recipe-update recipe-remove])
>events (chan)
<events (a/pub >events :type)]
(om/root app-view (om/root app-view
state state
{:target (.getElementById js/document "container") {:target (.getElementById js/document "container")
:shared {:grub-add grub-add :shared {:>events >events
:grub-update grub-update :<events <events
:grub-clear-all grub-clear-all :add-grubs-ch add-grubs-ch}})))
:grub-remove grub-remove
:recipe-add recipe-add
:recipe-add-grubs recipe-add-grubs
:recipe-update recipe-update
:recipe-remove recipe-remove
:>events >events
:<events <events}})
out))

View file

@ -7,32 +7,11 @@
(:require-macros [grub.macros :refer [log logs]] (:require-macros [grub.macros :refer [log logs]]
[cljs.core.async.macros :refer [go go-loop]])) [cljs.core.async.macros :refer [go go-loop]]))
(defn new-grub [grub] (defn new-grub [text]
{:id (str "grub-" (uuid/make-random)) {:id (str "grub-" (uuid/make-random))
:grub grub :text text
:completed false}) :completed false})
(defn add-event [grub]
(assoc (new-grub grub) :event :add-grub))
(defn add-list-event [grubs]
{:event :add-grub-list
:grubs grubs})
(defn edit-event [id grub]
{:event :update-grub
:id id
:grub grub})
(defn complete-event [{:keys [id completed]}]
{:event :update-grub
:id id
:completed (not completed)})
(defn remove-event [id]
{:event :remove-grub
:id id})
(def transitions (def transitions
{:waiting {:mouse-down :pressed {:waiting {:mouse-down :pressed
:touch-start :pressed} :touch-start :pressed}
@ -54,21 +33,18 @@
timeout-id (js/setTimeout timeout-fn 500)] timeout-id (js/setTimeout timeout-fn 500)]
(om/set-state! owner :timeout-id timeout-id)) (om/set-state! owner :timeout-id timeout-id))
[:pressed :waiting] (js/clearTimeout (om/get-state owner :timeout-id)) [:pressed :waiting] (js/clearTimeout (om/get-state owner :timeout-id))
[:editing :waiting] (let [update-ch (om/get-shared owner :grub-update) [:editing :waiting] (let [grub (om/get-props owner)]
id (:id @(om/get-props owner)) (om/transact! grub #(assoc % :text (om/get-state owner :grub-text))))
edit-event (edit-event id (om/get-state owner :grub))]
(put! update-ch edit-event))
nil) nil)
(om/set-state! owner :edit-state next))) (om/set-state! owner :edit-state next)))
(defn view [{:keys [id grub completed] :as props} owner] (defn view [{:keys [id text completed] :as grub} owner {:keys [remove-ch]}]
(reify (reify
om/IInitState om/IInitState
(init-state [_] (init-state [_]
(let [publisher (chan)] {:edit-state :waiting
{:edit-state :waiting :grub-text text
:grub grub :unmounted false})
:unmounted false}))
om/IRenderState om/IRenderState
(render-state [_ {:keys [edit-state] :as state}] (render-state [_ {:keys [edit-state] :as state}]
@ -77,9 +53,9 @@
{:class [(when completed "completed") {:class [(when completed "completed")
(when (= edit-state :pressed) "grub-active") (when (= edit-state :pressed) "grub-active")
(when (= edit-state :editing) "edit")] (when (= edit-state :editing) "edit")]
:on-click #(when (#{:waiting :pressed} edit-state) :on-click (fn [e] (when (#{:waiting :pressed} edit-state)
(put! (om/get-shared owner :grub-update) (complete-event @props)) (om/transact! grub #(assoc % :completed (not completed)))
(.blur (om/get-node owner :grub-input))) (.blur (om/get-node owner :grub-input))))
:on-mouse-down #(transition-state owner :mouse-down) :on-mouse-down #(transition-state owner :mouse-down)
:on-mouse-up #(transition-state owner :mouse-up) :on-mouse-up #(transition-state owner :mouse-up)
:on-mouse-leave #(transition-state owner :mouse-leave) :on-mouse-leave #(transition-state owner :mouse-leave)
@ -90,12 +66,12 @@
{:type "text" {:type "text"
:readOnly (if (= edit-state :editing) "" "readonly") :readOnly (if (= edit-state :editing) "" "readonly")
:ref :grub-input :ref :grub-input
:value (:grub state) :value (:grub-text state)
:on-change #(om/set-state! owner :grub (.. % -target -value)) :on-change #(om/set-state! owner :grub-text (.. % -target -value))
:on-key-up #(when (dom/enter-pressed? %) (transition-state owner :enter))}] :on-key-up #(when (dom/enter-pressed? %) (transition-state owner :enter))}]
(when (= edit-state :editing) (when (= edit-state :editing)
[:span.glyphicon.glyphicon-remove.pull-right [:span.glyphicon.glyphicon-remove.pull-right
{:on-click #(put! (om/get-shared owner :grub-remove) (remove-event id))}])])) {:on-click #(put! remove-ch id)}])]))
om/IDidMount om/IDidMount
(did-mount [_] (did-mount [_]

View file

@ -1,10 +1,9 @@
(ns grub.view.grub-list (ns grub.view.grub-list
(:require [om.core :as om :include-macros true] (:require [om.core :as om :include-macros true]
[sablono.core :as html :refer-macros [html]] [sablono.core :as html :refer-macros [html]]
[cljs.core.async :as a :refer [<! put! chan]] [cljs.core.async :as a :refer [<! chan]]
[grub.view.dom :as dom] [grub.view.dom :as dom]
[grub.view.grub :as grub-view] [grub.view.grub :as grub-view])
[cljs-uuid.core :as uuid])
(:require-macros [grub.macros :refer [log logs]] (:require-macros [grub.macros :refer [log logs]]
[cljs.core.async.macros :refer [go go-loop]])) [cljs.core.async.macros :refer [go go-loop]]))
@ -17,18 +16,20 @@
(defn sort-grubs [grubs] (defn sort-grubs [grubs]
(sort-by (juxt :completed get-grub-ingredient :grub) (vals grubs))) (sort-by (juxt :completed get-grub-ingredient :grub) (vals grubs)))
(defn add-grub [add new-grub owner] (defn add-grub [owner grubs new-grub-text]
(when (not (empty? new-grub)) (when (not (empty? new-grub-text))
(om/set-state! owner :new-grub "") (let [new-grub (grub-view/new-grub new-grub-text)]
(put! add (grub-view/add-event new-grub)))) (om/set-state! owner :new-grub-text "")
(om/transact! grubs #(assoc % (:id new-grub) new-grub)))))
(defn view [props owner] (defn view [grubs owner]
(reify (reify
om/IInitState om/IInitState
(init-state [_] (init-state [_]
{:new-grub ""}) {:new-grub-text ""
:remove-grub-ch (chan)})
om/IRenderState om/IRenderState
(render-state [this {:keys [new-grub] :as state}] (render-state [this {:keys [new-grub-text remove-grub-ch] :as state}]
(let [add (om/get-shared owner :grub-add)] (let [add (om/get-shared owner :grub-add)]
(html (html
[:div [:div
@ -38,22 +39,35 @@
[:input.form-control#add-grub-input [:input.form-control#add-grub-input
{:type "text" {:type "text"
:placeholder "What do you need?" :placeholder "What do you need?"
:value new-grub :value new-grub-text
:on-key-up #(when (dom/enter-pressed? %) :on-key-up #(when (dom/enter-pressed? %)
(add-grub add new-grub owner)) (add-grub owner grubs new-grub-text))
:on-change #(om/set-state! owner :new-grub (dom/event-val %))}]] :on-change #(om/set-state! owner :new-grub-text (dom/event-val %))}]]
[:button.btn.btn-primary [:button.btn.btn-primary
{:id "add-grub-btn" {:id "add-grub-btn"
:type "button" :type "button"
:on-click #(add-grub add new-grub owner)} :on-click #(add-grub owner grubs new-grub-text)}
[:span.glyphicon.glyphicon-plus]]] [:span.glyphicon.glyphicon-plus]]]
[:ul#grub-list.list-group [:ul#grub-list.list-group
(for [grub (sort-grubs props)] (for [grub (sort-grubs grubs)]
(om/build grub-view/view grub {:key :id}))] (om/build grub-view/view grub {:key :id :opts {:remove-ch remove-grub-ch}}))]
[:button.btn.pull-right [:button.btn.pull-right
{:id "clear-all-btn" {:id "clear-all-btn"
:class (when (empty? props) "hidden") :class (when (empty? grubs) "hidden")
:type "button" :type "button"
:on-click #(put! (om/get-shared owner :grub-clear-all) :on-click #(om/update! grubs {})}
{:event :clear-all-grubs})} "Clear all"]])))
"Clear all"]]))))) om/IWillMount
(will-mount [_]
(let [add-grubs-ch (om/get-shared owner :add-grubs-ch)
remove-grub-ch (om/get-state owner :remove-grub-ch)]
(go-loop []
(let [grubs-map (<! add-grubs-ch)]
(when-not (nil? grubs-map)
(om/transact! grubs #(merge % grubs-map))
(recur))))
(go-loop []
(let [id (<! remove-grub-ch)]
(when-not (nil? id)
(om/transact! grubs #(dissoc % id))
(recur))))))))

View file

@ -8,32 +8,26 @@
(:require-macros [grub.macros :refer [log logs]] (:require-macros [grub.macros :refer [log logs]]
[cljs.core.async.macros :refer [go go-loop]])) [cljs.core.async.macros :refer [go go-loop]]))
(defn add-event [name grubs] (defn new-recipe [name grubs]
{:event :add-recipe {:id (str "recipe-" (uuid/make-random))
:id (str "recipe-" (uuid/make-random))
:name name :name name
:grubs grubs}) :grubs grubs})
(defn update-event [id name grubs]
{:event :update-recipe
:id id
:name name
:grubs grubs})
(defn remove-event [id]
{:event :remove-recipe
:id id})
(defn parse-grubs-from-str [grubs-str] (defn parse-grubs-from-str [grubs-str]
(->> grubs-str (->> grubs-str
(clojure.string/split-lines) (clojure.string/split-lines)
(map grub-view/new-grub) (map grub-view/new-grub)
(into []))) (into [])))
(defn map-by-key [key coll]
(->> coll
(map (fn [a] [(get a key) a]))
(into {})))
(defn add-grubs [add-grubs-ch grubs-str] (defn add-grubs [add-grubs-ch grubs-str]
(let [grubs (parse-grubs-from-str grubs-str) (let [grubs (parse-grubs-from-str grubs-str)
event (grub-view/add-list-event grubs)] grubs-map (map-by-key :id grubs)]
(put! add-grubs-ch event))) (put! add-grubs-ch grubs-map)))
(def transitions (def transitions
{:waiting {:click :editing} {:waiting {:click :editing}
@ -44,38 +38,35 @@
(let [current (om/get-state owner :edit-state) (let [current (om/get-state owner :edit-state)
next (or (get-in transitions [current event]) current)] next (or (get-in transitions [current event]) current)]
(condp = [current next] (condp = [current next]
[:editing :waiting] (let [update-ch (om/get-shared owner :recipe-update) [:editing :waiting] (let [recipe (om/get-props owner)
id (:id @(om/get-props owner))
name (om/get-state owner :name) name (om/get-state owner :name)
grubs (om/get-state owner :grubs) grubs (om/get-state owner :grubs)]
event (update-event id name grubs)] (om/transact! recipe #(assoc % :name name :grubs grubs)))
(put! update-ch event))
nil) nil)
(om/set-state! owner :edit-state next))) (om/set-state! owner :edit-state next)))
(defn num-newlines [str] (defn num-newlines [str]
(count (re-seq #"\n" str))) (count (re-seq #"\n" str)))
(defn view [{:keys [id] :as props} owner] (defn view [{:keys [id] :as recipe} owner {:keys [remove-recipe-ch]}]
(reify (reify
om/IInitState om/IInitState
(init-state [_] (init-state [_]
(let [publisher (chan)] (let [publisher (chan)]
{:edit-state :waiting {:edit-state :waiting
:name (:name props) :name (:name recipe)
:grubs (:grubs props) :grubs (:grubs recipe)
:unmounted false})) :unmounted false}))
om/IWillReceiveProps om/IWillReceiveProps
(will-receive-props [this next-props] (will-receive-props [this next-recipe]
(om/set-state! owner :name (:name next-props)) (om/set-state! owner :name (:name next-recipe))
(om/set-state! owner :grubs (:grubs next-props))) (om/set-state! owner :grubs (:grubs next-recipe)))
om/IRenderState om/IRenderState
(render-state [this {:keys [edit-state name grubs]}] (render-state [this {:keys [edit-state name grubs]}]
(let [update (om/get-shared owner :recipe-update) (let [update (om/get-shared owner :recipe-update)]
add-grubs-ch (om/get-shared owner :recipe-add-grubs)]
(html (html
[:div.panel.panel-default.recipe-panel [:div.panel.panel-default.recipe-panel
{:on-click {:on-click
@ -92,7 +83,7 @@
{:type "button" {:type "button"
:class (when (= edit-state :editing) "hidden") :class (when (= edit-state :editing) "hidden")
:ref :add-grubs-btn :ref :add-grubs-btn
:on-click #(add-grubs add-grubs-ch grubs)} :on-click #(add-grubs (om/get-shared owner :add-grubs-ch) grubs)}
[:span.glyphicon.glyphicon-plus] [:span.glyphicon.glyphicon-plus]
" Grubs"]] " Grubs"]]
[:div.panel-body.recipe-grubs [:div.panel-body.recipe-grubs
@ -105,7 +96,7 @@
:on-change #(om/set-state! owner :grubs (dom/event-val %))}] :on-change #(om/set-state! owner :grubs (dom/event-val %))}]
[:button.btn.btn-danger.pull-left.recipe-remove-btn [:button.btn.btn-danger.pull-left.recipe-remove-btn
{:type "button" {:type "button"
:on-click #(put! (om/get-shared owner :recipe-remove) (remove-event id))} :on-click #(put! remove-recipe-ch id)}
[:span.glyphicon.glyphicon-trash]] [:span.glyphicon.glyphicon-trash]]
[:button.btn.btn-primary.pull-right.recipe-done-btn [:button.btn.btn-primary.pull-right.recipe-done-btn
{:type "button" {:type "button"

View file

@ -1,7 +1,7 @@
(ns grub.view.recipe-list (ns grub.view.recipe-list
(:require [om.core :as om :include-macros true] (:require [om.core :as om :include-macros true]
[sablono.core :as html :refer-macros [html]] [sablono.core :as html :refer-macros [html]]
[cljs.core.async :as a :refer [<! put! chan]] [cljs.core.async :as a :refer [<! chan]]
[cljs-uuid.core :as uuid] [cljs-uuid.core :as uuid]
[grub.view.dom :as dom] [grub.view.dom :as dom]
[grub.view.grub :as grub-view] [grub.view.grub :as grub-view]
@ -9,12 +9,14 @@
(:require-macros [grub.macros :refer [log logs]] (:require-macros [grub.macros :refer [log logs]]
[cljs.core.async.macros :refer [go go-loop]])) [cljs.core.async.macros :refer [go go-loop]]))
(defn add-recipe [ch name grubs owner] (defn add-recipe [owner name grubs]
(when (and (not (empty? name)) (when (and (not (empty? name))
(not (empty? grubs))) (not (empty? grubs)))
(om/set-state! owner :new-recipe-name "") (let [recipes (om/get-props owner)
(om/set-state! owner :new-recipe-grubs "") new-recipe (recipe/new-recipe name grubs)]
(put! ch (recipe/add-event name grubs)))) (om/set-state! owner :new-recipe-name "")
(om/set-state! owner :new-recipe-grubs "")
(om/transact! recipes #(assoc % (:id new-recipe) new-recipe)))))
(def transitions (def transitions
{:waiting {:click :editing} {:waiting {:click :editing}
@ -28,7 +30,7 @@
[:editing :save :waiting] (let [add-ch (om/get-shared owner :recipe-add) [:editing :save :waiting] (let [add-ch (om/get-shared owner :recipe-add)
name (om/get-state owner :new-recipe-name) name (om/get-state owner :new-recipe-name)
grubs (om/get-state owner :new-recipe-grubs)] grubs (om/get-state owner :new-recipe-grubs)]
(add-recipe add-ch name grubs owner)) (add-recipe owner name grubs))
nil) nil)
(om/set-state! owner :edit-state next))) (om/set-state! owner :edit-state next)))
@ -36,11 +38,10 @@
(reify (reify
om/IInitState om/IInitState
(init-state [_] (init-state [_]
(let [publisher (chan)] {:edit-state :waiting
{:edit-state :waiting :new-recipe-name ""
:new-recipe-name "" :new-recipe-grubs ""
:new-recipe-grubs "" :unmounted false})
:unmounted false}))
om/IRenderState om/IRenderState
(render-state [this {:keys [edit-state new-recipe-name new-recipe-grubs]}] (render-state [this {:keys [edit-state new-recipe-name new-recipe-grubs]}]
@ -67,7 +68,7 @@
{:type "button" {:type "button"
:ref :save-btn :ref :save-btn
:on-click #(transition-state owner :save)} :on-click #(transition-state owner :save)}
"Save"]]])) [:span.glyphicon.glyphicon-ok]]]]))
om/IWillMount om/IWillMount
(will-mount [_] (will-mount [_]
@ -92,12 +93,24 @@
(defn view [recipes owner] (defn view [recipes owner]
(reify (reify
om/IRender om/IInitState
(render [this] (init-state [_]
{:remove-recipe-ch (chan)})
om/IRenderState
(render-state [_ {:keys [remove-recipe-ch]}]
(html (html
[:div [:div
[:h3.recipes-title "Recipes"] [:h3.recipes-title "Recipes"]
(om/build new-recipe-view recipes) (om/build new-recipe-view recipes)
[:ul#recipe-list.list-group.recipe-list [:ul#recipe-list.list-group.recipe-list
(for [recipe (vals recipes)] (for [recipe (vals recipes)]
(om/build recipe/view recipe {:key :id}))]])))) (om/build recipe/view
recipe
{:key :id :opts {:remove-recipe-ch remove-recipe-ch}}))]]))
om/IWillMount
(will-mount [_]
(let [remove-recipe-ch (om/get-state owner :remove-recipe-ch)]
(go-loop []
(let [removed-id (<! remove-recipe-ch)]
(when-not (nil? removed-id)
(om/transact! recipes #(dissoc % removed-id)))))))))