Grubs can be edited by long-pressing
This commit is contained in:
parent
f8abd291ef
commit
f4eaf7cef9
11 changed files with 372 additions and 195 deletions
|
@ -1,3 +1,11 @@
|
||||||
|
html, body {
|
||||||
|
height: 100%;
|
||||||
|
min-height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 100%;
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
@ -27,16 +35,47 @@ tr:hover .grub-close {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grub-item:active {
|
.grub-item.grub-active {
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
background-color: rgb(71, 73, 73);
|
background-color: rgb(71, 73, 73);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.grub-item .input-span {
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grub-item .grub-static {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grub-item .grub-input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grub-item.edit .grub-static {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grub-item.edit .grub-input {
|
||||||
|
display: inline;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grub-item .glyphicon {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.completed {
|
.completed {
|
||||||
text-decoration: line-through;
|
text-decoration: line-through;
|
||||||
color: #a9a9a9;
|
color: #a9a9a9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.completed.edit {
|
||||||
|
text-decoration: none;
|
||||||
|
color: default;
|
||||||
|
}
|
||||||
|
|
||||||
#clear-all-btn {
|
#clear-all-btn {
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,18 @@
|
||||||
{:_id (:_id test-grub) :completed false}
|
{:_id (:_id test-grub) :completed false}
|
||||||
(mc/find-one-as-map db/grub-collection {:_id (:_id test-grub)})))))
|
(mc/find-one-as-map db/grub-collection {:_id (:_id test-grub)})))))
|
||||||
|
|
||||||
|
(describe "Update"
|
||||||
|
(it "should update grub info when an update event comes"
|
||||||
|
(let [test-grub {:_id 123456 :grub "original"}]
|
||||||
|
(mc/insert db/grub-collection test-grub)
|
||||||
|
(>!! @db/incoming-events {:event :update
|
||||||
|
:_id (:_id test-grub)
|
||||||
|
:grub "updated"})
|
||||||
|
(short-delay)
|
||||||
|
(should=
|
||||||
|
{:_id (:_id test-grub) :grub "updated"}
|
||||||
|
(mc/find-one-as-map db/grub-collection {:_id (:_id test-grub)})))))
|
||||||
|
|
||||||
(describe "Delete"
|
(describe "Delete"
|
||||||
(it "should delete a grub when a delete event comes"
|
(it "should delete a grub when a delete event comes"
|
||||||
(let [test-grub {:_id 123456 :completed true}]
|
(let [test-grub {:_id 123456 :completed true}]
|
||||||
|
|
|
@ -56,22 +56,4 @@
|
||||||
clear-all-event {:event :clear-all}]
|
clear-all-event {:event :clear-all}]
|
||||||
(reset! state/grubs [test-grub])
|
(reset! state/grubs [test-grub])
|
||||||
(state/handle-event clear-all-event)
|
(state/handle-event clear-all-event)
|
||||||
(should= [] @state/grubs)))))
|
(should= [] @state/grubs))))))
|
||||||
|
|
||||||
(describe
|
|
||||||
"view event handling"
|
|
||||||
(describe "Create"
|
|
||||||
(it "should add a new grub to the state when a create event comes"
|
|
||||||
(let [test-grub {:grub "testgrub"}
|
|
||||||
create-event (assoc test-grub :event :create)]
|
|
||||||
(state/handle-view-event create-event)
|
|
||||||
(js/setTimeout (fn [] (let [created-grub (first @state/grubs)]
|
|
||||||
(should= (:grub test-grub) (:grub created-grub)))))))
|
|
||||||
(it "should generate an _id for the new grub"
|
|
||||||
(let [test-grub {:grub "testgrub"}
|
|
||||||
create-event (assoc test-grub :event :create)]
|
|
||||||
(state/handle-view-event create-event)
|
|
||||||
(js/setTimeout (fn []
|
|
||||||
(let [added-grub (first (filter #(= (:grub %) (:grub test-grub))
|
|
||||||
@state/grubs))]
|
|
||||||
(should-not-be-nil (:_id added-grub))))))))))
|
|
||||||
|
|
|
@ -29,6 +29,11 @@
|
||||||
{:_id (:_id event)}
|
{:_id (:_id event)}
|
||||||
{mo/$set {:completed false}}))
|
{mo/$set {:completed false}}))
|
||||||
|
|
||||||
|
(defmethod handle-event :update [event]
|
||||||
|
(mc/update grub-collection
|
||||||
|
{:_id (:_id event)}
|
||||||
|
{mo/$set {:grub (:grub event)}}))
|
||||||
|
|
||||||
(defmethod handle-event :delete [event]
|
(defmethod handle-event :delete [event]
|
||||||
(mc/remove grub-collection {:_id (:_id event)}))
|
(mc/remove grub-collection {:_id (:_id event)}))
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,15 @@
|
||||||
(ns grub.async-utils
|
(ns grub.async-utils
|
||||||
(:require [cljs.core.async :as async :refer [<! >! chan put! alts!]])
|
(:refer-clojure :exclude [map filter])
|
||||||
(:require-macros [cljs.core.async.macros :as m :refer [go]]
|
(:require [cljs.core.async :as async :refer [<! >! chan put! alts! close!]])
|
||||||
[grub.macros :refer [go-loop]]))
|
(:require-macros [cljs.core.async.macros :refer [go]]
|
||||||
|
[grub.macros :refer [do-chan]]))
|
||||||
|
|
||||||
|
(defn log [in]
|
||||||
|
(let [out (chan)]
|
||||||
|
(do-chan [e in]
|
||||||
|
(.log js/console e)
|
||||||
|
(>! out e))
|
||||||
|
out))
|
||||||
|
|
||||||
(defn put-all! [cs x]
|
(defn put-all! [cs x]
|
||||||
(doseq [c cs]
|
(doseq [c cs]
|
||||||
|
@ -22,53 +30,57 @@
|
||||||
|
|
||||||
(defn fan-in
|
(defn fan-in
|
||||||
([ins] (fan-in (chan) ins))
|
([ins] (fan-in (chan) ins))
|
||||||
([c ins]
|
([out ins]
|
||||||
(go-loop
|
(go (loop [ins (vec ins)]
|
||||||
(let [[x] (alts! ins)]
|
(when (> (count ins) 0)
|
||||||
(>! c x)))
|
(let [[x in] (alts! ins)]
|
||||||
c))
|
(when x
|
||||||
|
(>! out x)
|
||||||
|
(recur ins))
|
||||||
|
(recur (vec (disj (set ins) in))))))
|
||||||
|
(close! out))
|
||||||
|
out))
|
||||||
|
|
||||||
(defn copy-chan
|
(defn copy
|
||||||
([c]
|
([c]
|
||||||
(first (fan-out c 1)))
|
(first (fan-out c 1)))
|
||||||
([out c]
|
([out c]
|
||||||
(first (fan-out c [out]))))
|
(first (fan-out c [out]))))
|
||||||
|
|
||||||
(defn event-chan
|
(defn map [f in]
|
||||||
([type] (event-chan js/window type))
|
|
||||||
([el type] (event-chan (chan) el type))
|
|
||||||
([c el type]
|
|
||||||
(let [writer #(put! c %)]
|
|
||||||
(.addEventListener el type writer)
|
|
||||||
{:chan c
|
|
||||||
:unsubscribe #(.removeEventListener el type writer)})))
|
|
||||||
|
|
||||||
(defn map-chan
|
|
||||||
([f source] (map-chan (chan) f source))
|
|
||||||
([c f source]
|
|
||||||
(go-loop
|
|
||||||
(>! c (f (<! source))))
|
|
||||||
c))
|
|
||||||
|
|
||||||
(defn filter-chan
|
|
||||||
([f source] (filter-chan (chan) f source))
|
|
||||||
([c f source]
|
|
||||||
(go-loop
|
|
||||||
(let [v (<! source)]
|
|
||||||
(when (f v)
|
|
||||||
(>! c v))))
|
|
||||||
c))
|
|
||||||
|
|
||||||
(defn do-chan! [f source]
|
|
||||||
(go-loop
|
|
||||||
(let [v (<! source)]
|
|
||||||
(f v))))
|
|
||||||
|
|
||||||
(defn do-chan [f source]
|
|
||||||
(let [out (chan)]
|
(let [out (chan)]
|
||||||
(go-loop
|
(go (loop []
|
||||||
(let [v (<! source)]
|
(if-let [x (<! in)]
|
||||||
(f v)
|
(do (>! out (f x))
|
||||||
(>! out v)))
|
(recur))
|
||||||
|
(close! out))))
|
||||||
out))
|
out))
|
||||||
|
|
||||||
|
(defn map-filter [f in]
|
||||||
|
(let [out (chan)]
|
||||||
|
(go (loop []
|
||||||
|
(if-let [x (<! in)]
|
||||||
|
(do
|
||||||
|
(when-let [val (f x)]
|
||||||
|
(>! out val))
|
||||||
|
(recur))
|
||||||
|
(close! out))))
|
||||||
|
out))
|
||||||
|
|
||||||
|
(defn filter [pred in]
|
||||||
|
(let [out (chan)]
|
||||||
|
(go (loop []
|
||||||
|
(if-let [x (<! in)]
|
||||||
|
(do (when (pred x) (>! out x))
|
||||||
|
(recur))
|
||||||
|
(close! out))))
|
||||||
|
out))
|
||||||
|
|
||||||
|
(defn siphon
|
||||||
|
([in] (siphon in []))
|
||||||
|
([in coll]
|
||||||
|
(go (loop [coll coll]
|
||||||
|
(if-let [v (<! in)]
|
||||||
|
(recur (conj coll v))
|
||||||
|
coll)))))
|
||||||
|
|
||||||
|
|
|
@ -9,13 +9,14 @@
|
||||||
[cljs.core.async.macros :refer [go]]))
|
[cljs.core.async.macros :refer [go]]))
|
||||||
|
|
||||||
(defn handle-grub-events []
|
(defn handle-grub-events []
|
||||||
(a/copy-chan state/incoming-view-events view/outgoing-events)
|
(a/fan-out view/outgoing-events [state/incoming-events ws/incoming-events])
|
||||||
(a/copy-chan state/incoming-events ws/outgoing-events)
|
(a/copy state/incoming-events ws/outgoing-events)
|
||||||
(a/copy-chan ws/incoming-events state/outgoing-events))
|
(a/copy ws/incoming-events state/outgoing-events))
|
||||||
|
|
||||||
(defn init []
|
(defn init []
|
||||||
(view/init)
|
|
||||||
(ws/connect-to-server)
|
(ws/connect-to-server)
|
||||||
|
(state/init)
|
||||||
|
(view/init)
|
||||||
(handle-grub-events))
|
(handle-grub-events))
|
||||||
|
|
||||||
(init)
|
(init)
|
||||||
|
|
120
src/cljs/grub/dom.cljs
Normal file
120
src/cljs/grub/dom.cljs
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
(ns grub.dom
|
||||||
|
(:require [grub.async-utils :as a]
|
||||||
|
[dommy.core :as dommy]
|
||||||
|
[cljs.core.async :refer [<! >! chan timeout close!]])
|
||||||
|
(:require-macros [grub.macros :refer [log logs go-loop]]
|
||||||
|
[dommy.macros :refer [deftemplate sel1 node]]
|
||||||
|
[cljs.core.async.macros :refer [go]]))
|
||||||
|
|
||||||
|
(defn listen
|
||||||
|
([el type] (listen el type nil))
|
||||||
|
([el type f] (listen el type f (chan)))
|
||||||
|
([el type f out]
|
||||||
|
(let [push-fn (fn [e] (when f (f e)) (go (>! out e)))
|
||||||
|
unlisten #(do (dommy/unlisten! el type push-fn)
|
||||||
|
(close! out))]
|
||||||
|
(dommy/listen! el type push-fn)
|
||||||
|
{:chan out :unlisten unlisten})))
|
||||||
|
|
||||||
|
(defn listen-once
|
||||||
|
([el type] (listen el type nil))
|
||||||
|
([el type f] (listen el type f (chan)))
|
||||||
|
([el type f out]
|
||||||
|
(let [push-fn (fn [e] (when f (f e)) (go (>! out e)))
|
||||||
|
unlisten #(do (dommy/unlisten! el type push-fn)
|
||||||
|
(close! out))]
|
||||||
|
(dommy/listen-once! el type push-fn)
|
||||||
|
{:chan out :unlisten unlisten})))
|
||||||
|
|
||||||
|
(def add-grub-text
|
||||||
|
(node [:input.form-control {:id "add-grub-input" :type "text" :placeholder "2 grubs"}]))
|
||||||
|
|
||||||
|
(def add-grub-btn
|
||||||
|
(node [:button.btn.btn-primary {:id "add-grub-btn" :type "button"} "Add"]))
|
||||||
|
|
||||||
|
(def clear-all-btn
|
||||||
|
(node [:button.btn.hidden {:id "clear-all-btn" :type "button"} "Clear all"]))
|
||||||
|
|
||||||
|
(defn make-grub-node [grub]
|
||||||
|
(node [:li.list-group-item.grub-item
|
||||||
|
{:id (:_id grub)
|
||||||
|
:class (when (:completed grub) "completed")}
|
||||||
|
[:span.grub-static
|
||||||
|
(if (:completed grub)
|
||||||
|
[:span.glyphicon.glyphicon-check]
|
||||||
|
[:span.glyphicon.glyphicon-unchecked])
|
||||||
|
[:span.grub-text (:grub grub)]]
|
||||||
|
[:input.grub-input {:type "text" :value (:grub grub)}]]))
|
||||||
|
|
||||||
|
(defn grubs-selector []
|
||||||
|
[(sel1 :#grub-list) :.grub-item])
|
||||||
|
|
||||||
|
(deftemplate main-template []
|
||||||
|
[:div.container
|
||||||
|
[:div.row.show-grid
|
||||||
|
[:div.col-lg-4]
|
||||||
|
[:div.col-lg-4
|
||||||
|
[:h3 "Grub List"]
|
||||||
|
[:div.input-group
|
||||||
|
add-grub-text
|
||||||
|
[:span.input-group-btn
|
||||||
|
add-grub-btn]]
|
||||||
|
[:ul#grub-list.list-group]
|
||||||
|
clear-all-btn]
|
||||||
|
[:div.col-lg-4]
|
||||||
|
[:div.col-lg-2]]])
|
||||||
|
|
||||||
|
(defn render-body []
|
||||||
|
(dommy/prepend! (sel1 :body) (main-template)))
|
||||||
|
|
||||||
|
(defn render-grub-list [grubs]
|
||||||
|
(let [grub-list (sel1 :#grub-list)
|
||||||
|
sorted-grubs (sort-by (juxt :completed :_id) grubs)]
|
||||||
|
(aset grub-list "innerHTML" "")
|
||||||
|
(doseq [grub sorted-grubs]
|
||||||
|
(let [node (make-grub-node grub)]
|
||||||
|
(dommy/append! grub-list node)))))
|
||||||
|
|
||||||
|
(defn get-add-grub-text []
|
||||||
|
(dommy/value add-grub-text))
|
||||||
|
|
||||||
|
(defn clear-add-grub-text []
|
||||||
|
(dommy/set-value! add-grub-text ""))
|
||||||
|
|
||||||
|
|
||||||
|
(defprotocol IActivatable
|
||||||
|
(-activate! [view])
|
||||||
|
(-deactivate! [view]))
|
||||||
|
|
||||||
|
(defprotocol IHideable
|
||||||
|
(-hide! [view])
|
||||||
|
(-show! [view]))
|
||||||
|
|
||||||
|
(defprotocol IEditable
|
||||||
|
(-set-editing! [view])
|
||||||
|
(-unset-editing! [view]))
|
||||||
|
|
||||||
|
(extend-type js/HTMLElement
|
||||||
|
IActivatable
|
||||||
|
(-activate! [view]
|
||||||
|
(dommy/add-class! view :grub-active))
|
||||||
|
(-deactivate! [view]
|
||||||
|
(dommy/remove-class! view :grub-active)))
|
||||||
|
|
||||||
|
(extend-type js/HTMLElement
|
||||||
|
IHideable
|
||||||
|
(-hide! [view]
|
||||||
|
(dommy/add-class! view :hidden))
|
||||||
|
(-show! [view]
|
||||||
|
(dommy/remove-class! view :hidden)))
|
||||||
|
|
||||||
|
(extend-type js/HTMLLIElement
|
||||||
|
IEditable
|
||||||
|
(-set-editing! [view]
|
||||||
|
(-deactivate! view)
|
||||||
|
(dommy/add-class! view :edit)
|
||||||
|
(.focus (sel1 view :input)))
|
||||||
|
(-unset-editing! [view]
|
||||||
|
(dommy/remove-class! view :edit)))
|
||||||
|
|
||||||
|
|
|
@ -11,3 +11,13 @@
|
||||||
`(cljs.core.async.macros/go
|
`(cljs.core.async.macros/go
|
||||||
(while true
|
(while true
|
||||||
~@body)))
|
~@body)))
|
||||||
|
|
||||||
|
(defmacro do-chan [[binding chan] & body]
|
||||||
|
`(let [chan# ~chan]
|
||||||
|
(cljs.core.async.macros/go
|
||||||
|
(loop []
|
||||||
|
(if-let [~binding (cljs.core.async/<! chan#)]
|
||||||
|
(do
|
||||||
|
~@body
|
||||||
|
(recur))
|
||||||
|
:done)))))
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
(ns grub.state
|
(ns grub.state
|
||||||
(:require [cljs.core.async :refer [chan <!]])
|
(:require [grub.async-utils :as a]
|
||||||
|
[cljs.core.async :refer [chan <!]])
|
||||||
(:require-macros [grub.macros :refer [log logs go-loop]]
|
(:require-macros [grub.macros :refer [log logs go-loop]]
|
||||||
[cljs.core.async.macros :refer [go]]))
|
[cljs.core.async.macros :refer [go]]))
|
||||||
|
|
||||||
(def incoming-events (chan))
|
(def incoming-events (chan))
|
||||||
(def incoming-view-events (chan))
|
|
||||||
(def outgoing-events (chan))
|
(def outgoing-events (chan))
|
||||||
|
|
||||||
(def grubs (atom []))
|
(def grubs (atom []))
|
||||||
|
@ -38,6 +38,13 @@
|
||||||
incomplete-grub (assoc grub :completed false)]
|
incomplete-grub (assoc grub :completed false)]
|
||||||
(assoc current grub-index incomplete-grub)))))
|
(assoc current grub-index incomplete-grub)))))
|
||||||
|
|
||||||
|
(defmethod handle-event :update [event]
|
||||||
|
(swap! grubs
|
||||||
|
(fn [current]
|
||||||
|
(let [[grub-index grub] (get-grub-with-index current (:_id event))
|
||||||
|
updated-grub (assoc grub :grub (:grub event))]
|
||||||
|
(assoc current grub-index updated-grub)))))
|
||||||
|
|
||||||
(defmethod handle-event :delete [event]
|
(defmethod handle-event :delete [event]
|
||||||
(swap! grubs
|
(swap! grubs
|
||||||
(fn [current]
|
(fn [current]
|
||||||
|
@ -49,33 +56,9 @@
|
||||||
(defmethod handle-event :unknown-event [event]
|
(defmethod handle-event :unknown-event [event]
|
||||||
(logs "Cannot handle unknown event:" event))
|
(logs "Cannot handle unknown event:" event))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(defn pass-on-view-event [event]
|
|
||||||
(go (>! incoming-events event))
|
|
||||||
(go (>! outgoing-events event)))
|
|
||||||
|
|
||||||
(defmulti handle-view-event :event :default :unknown-event)
|
|
||||||
|
|
||||||
(defmethod handle-view-event :create [event]
|
|
||||||
(let [create-event (-> event
|
|
||||||
(assoc :event :add)
|
|
||||||
(assoc :_id (str "grub-" (.now js/Date)))
|
|
||||||
(assoc :completed false))]
|
|
||||||
(pass-on-view-event create-event)))
|
|
||||||
|
|
||||||
(defmethod handle-view-event :unknown-event [event]
|
|
||||||
(pass-on-view-event event))
|
|
||||||
|
|
||||||
|
|
||||||
(defn handle-incoming-events []
|
(defn handle-incoming-events []
|
||||||
(go-loop (handle-event (<! incoming-events))))
|
(go-loop (let [event (<! incoming-events)]
|
||||||
|
(handle-event event))))
|
||||||
|
|
||||||
(defn handle-incoming-view-events []
|
|
||||||
(go-loop (handle-view-event (<! incoming-view-events))))
|
|
||||||
|
|
||||||
(defn init []
|
(defn init []
|
||||||
(handle-incoming-events)
|
(handle-incoming-events))
|
||||||
(handle-incoming-view-events))
|
|
||||||
|
|
||||||
(init)
|
|
||||||
|
|
|
@ -1,122 +1,135 @@
|
||||||
(ns grub.view
|
(ns grub.view
|
||||||
(:require [grub.async-utils
|
(:require [grub.async-utils :as a]
|
||||||
:refer [do-chan! do-chan event-chan map-chan fan-in filter-chan]]
|
|
||||||
[grub.state :as state]
|
[grub.state :as state]
|
||||||
|
[grub.dom :as dom]
|
||||||
[dommy.core :as dommy]
|
[dommy.core :as dommy]
|
||||||
[cljs.core.async :refer [<! >! chan]])
|
[cljs.core.async :refer [<! >! chan timeout close!]])
|
||||||
(:require-macros [grub.macros :refer [log logs go-loop]]
|
(:require-macros [grub.macros :refer [log logs go-loop do-chan]]
|
||||||
[dommy.macros :refer [deftemplate sel1 node]]
|
[dommy.macros :refer [deftemplate sel1 node]]
|
||||||
[cljs.core.async.macros :refer [go]]))
|
[cljs.core.async.macros :refer [go]]))
|
||||||
|
|
||||||
(def outgoing-events (chan))
|
(def outgoing-events (chan))
|
||||||
|
|
||||||
(def add-grub-text
|
|
||||||
(node [:input.form-control {:id "add-grub-input" :type "text" :placeholder "2 grubs"}]))
|
|
||||||
|
|
||||||
(def add-grub-btn
|
|
||||||
(node [:button.btn.btn-primary {:id "add-grub-btn" :type "button"} "Add"]))
|
|
||||||
|
|
||||||
(deftemplate main-template []
|
|
||||||
[:div.container
|
|
||||||
[:div.row.show-grid
|
|
||||||
[:div.col-lg-4]
|
|
||||||
[:div.col-lg-4
|
|
||||||
[:h3 "Grub List"]
|
|
||||||
[:div.input-group
|
|
||||||
add-grub-text
|
|
||||||
[:span.input-group-btn
|
|
||||||
add-grub-btn]]
|
|
||||||
[:ul#grub-list.list-group]
|
|
||||||
[:button.btn.hidden {:id "clear-all-btn" :type "button"} "Clear all"]]
|
|
||||||
[:div.col-lg-4]
|
|
||||||
[:div.col-lg-2]]])
|
|
||||||
|
|
||||||
(defn make-grub-node [grub]
|
|
||||||
(if (:completed grub)
|
|
||||||
(node [:li.list-group-item.completed.grub-item {:id (:_id grub)}
|
|
||||||
[:span.glyphicon.glyphicon-check]
|
|
||||||
(str " " (:grub grub))])
|
|
||||||
(node [:li.list-group-item.grub-item {:id (:_id grub)}
|
|
||||||
[:span.glyphicon.glyphicon-unchecked]
|
|
||||||
(str " " (:grub grub))])))
|
|
||||||
|
|
||||||
(defn render-body []
|
|
||||||
(dommy/prepend! (sel1 :body) (main-template)))
|
|
||||||
|
|
||||||
(defprotocol IHideable
|
|
||||||
(-hide! [view])
|
|
||||||
(-show! [view]))
|
|
||||||
|
|
||||||
(extend-type js/HTMLElement
|
|
||||||
IHideable
|
|
||||||
(-hide! [view]
|
|
||||||
(dommy/add-class! view :hidden))
|
|
||||||
(-show! [view]
|
|
||||||
(dommy/remove-class! view :hidden)))
|
|
||||||
|
|
||||||
(defn get-add-grub-text []
|
|
||||||
(let [text (dommy/value add-grub-text)]
|
|
||||||
(dommy/set-value! add-grub-text "")
|
|
||||||
text))
|
|
||||||
|
|
||||||
(defn get-grubs-from-clicks []
|
(defn get-grubs-from-clicks []
|
||||||
(let [out (chan)]
|
(->> (:chan (dom/listen dom/add-grub-btn :click))
|
||||||
(dommy/listen! add-grub-btn :click #(go (>! out (get-add-grub-text))))
|
(a/map #(dom/get-add-grub-text))))
|
||||||
out))
|
|
||||||
|
|
||||||
(defn put-grubs-if-enter-pressed [out event]
|
|
||||||
(when (= (.-keyIdentifier event) "Enter")
|
|
||||||
(go (>! out (get-add-grub-text)))))
|
|
||||||
|
|
||||||
(defn get-grubs-from-enter []
|
(defn get-grubs-from-enter []
|
||||||
(let [out (chan)]
|
(->> (:chan (dom/listen dom/add-grub-text :keyup))
|
||||||
(dommy/listen! add-grub-text
|
(a/filter #(= (.-keyIdentifier %) "Enter"))
|
||||||
:keyup
|
(a/map #(dom/get-add-grub-text))))
|
||||||
(partial put-grubs-if-enter-pressed out))
|
|
||||||
out))
|
|
||||||
|
|
||||||
(defn get-added-events []
|
(defn get-created-events []
|
||||||
(let [grubs (fan-in [(get-grubs-from-clicks)
|
(let [grubs (a/fan-in [(get-grubs-from-clicks)
|
||||||
(get-grubs-from-enter)])]
|
(get-grubs-from-enter)])]
|
||||||
(->> grubs
|
(->> grubs
|
||||||
(filter-chan #(not (empty? %)))
|
(a/filter #(not (empty? %)))
|
||||||
(map-chan (fn [g] {:event :create :grub g})))))
|
(a/map (fn [g] {:grub g})))))
|
||||||
|
|
||||||
(defn get-completed-event [event]
|
(defn get-clear-all-events []
|
||||||
|
(:chan (dom/listen dom/clear-all-btn :click)))
|
||||||
|
|
||||||
|
(defn get-grub-mousedown-events []
|
||||||
|
(:chan (dom/listen (dom/grubs-selector) :mousedown)))
|
||||||
|
|
||||||
|
(defn get-grub-mouseup-events []
|
||||||
|
(:chan (dom/listen (dom/grubs-selector) :mouseup)))
|
||||||
|
|
||||||
|
(defn get-grub-mouseleave-events []
|
||||||
|
(:chan (dom/listen (dom/grubs-selector) :mouseleave)))
|
||||||
|
|
||||||
|
(defn get-body-clicks []
|
||||||
|
(:chan (dom/listen (sel1 :body) :click)))
|
||||||
|
|
||||||
|
(defn get-enters []
|
||||||
|
(->> (:chan (dom/listen (sel1 :body) :keyup))
|
||||||
|
(a/filter #(= (.-keyIdentifier %) "Enter"))))
|
||||||
|
|
||||||
|
(defn parse-completed-event [event]
|
||||||
(let [target (.-target event)
|
(let [target (.-target event)
|
||||||
id (.-id target)
|
id (.-id target)
|
||||||
completed (dommy/has-class? target "completed")
|
completed (dommy/has-class? target "completed")
|
||||||
event-type (if completed :uncomplete :complete)]
|
event-type (if completed :uncomplete :complete)]
|
||||||
{:_id id :event event-type}))
|
{:_id id :event event-type}))
|
||||||
|
|
||||||
(defn get-clear-all-events []
|
|
||||||
(let [events (chan)]
|
|
||||||
(dommy/listen! (sel1 :#clear-all-btn) :click #(go (>! events {:event :clear-all})))
|
|
||||||
events))
|
|
||||||
|
|
||||||
(defn render-grub-list [grubs]
|
(defn re-render-when-state-changes []
|
||||||
(let [grub-list (sel1 :#grub-list)
|
|
||||||
sorted-grubs (sort-by (juxt :completed :_id) grubs)]
|
|
||||||
(aset grub-list "innerHTML" "")
|
|
||||||
(doseq [grub sorted-grubs]
|
|
||||||
(let [node (make-grub-node grub)]
|
|
||||||
(dommy/listen! node :click #(go (>! outgoing-events (get-completed-event %))))
|
|
||||||
(dommy/append! grub-list node)))))
|
|
||||||
|
|
||||||
(defn push-outgoing-events []
|
|
||||||
(fan-in outgoing-events [(get-added-events)
|
|
||||||
(get-clear-all-events)]))
|
|
||||||
|
|
||||||
(defn watch-for-state-changes []
|
|
||||||
(add-watch state/grubs
|
(add-watch state/grubs
|
||||||
:grub-add-watch
|
:grub-add-watch
|
||||||
(fn [key ref old new]
|
(fn [key ref old new]
|
||||||
(if (empty? new)
|
(if (empty? new)
|
||||||
(-hide! (sel1 :#clear-all-btn))
|
(dom/-hide! dom/clear-all-btn)
|
||||||
(-show! (sel1 :#clear-all-btn)))
|
(dom/-show! dom/clear-all-btn))
|
||||||
(render-grub-list new))))
|
(dom/render-grub-list new))))
|
||||||
|
|
||||||
|
(defn event-loop []
|
||||||
|
(let [out (chan)
|
||||||
|
created (get-created-events)
|
||||||
|
clear-all (get-clear-all-events)
|
||||||
|
mousedown (get-grub-mousedown-events)
|
||||||
|
mouseup (get-grub-mouseup-events)
|
||||||
|
mouseleave (get-grub-mouseleave-events)
|
||||||
|
body-click (get-body-clicks)
|
||||||
|
edit (chan)
|
||||||
|
enter (get-enters)]
|
||||||
|
(go (loop [mousedown-time nil
|
||||||
|
edit-elem nil]
|
||||||
|
(let [[event c] (alts! [created clear-all mousedown
|
||||||
|
mouseup mouseleave edit body-click
|
||||||
|
enter])]
|
||||||
|
(if edit-elem
|
||||||
|
(cond
|
||||||
|
(or (and (= c body-click)
|
||||||
|
(not (dommy/descendant? (.-target event) edit-elem)))
|
||||||
|
(= c enter))
|
||||||
|
(do (dom/-unset-editing! edit-elem)
|
||||||
|
(let [grub-text (.-value (sel1 edit-elem :.grub-input))
|
||||||
|
id (.-id edit-elem)]
|
||||||
|
(>! out {:event :update
|
||||||
|
:grub grub-text
|
||||||
|
:_id id}))
|
||||||
|
(recur nil nil))
|
||||||
|
|
||||||
|
:else (recur nil edit-elem))
|
||||||
|
(cond
|
||||||
|
(= c created)
|
||||||
|
(let [add-event (-> event
|
||||||
|
(assoc :event :add)
|
||||||
|
(assoc :_id (str "grub-" (.now js/Date)))
|
||||||
|
(assoc :completed false))]
|
||||||
|
(>! out add-event)
|
||||||
|
(dom/clear-add-grub-text)
|
||||||
|
(recur mousedown-time edit-elem))
|
||||||
|
|
||||||
|
(= c clear-all)
|
||||||
|
(do (>! out {:event :clear-all})
|
||||||
|
(recur mousedown-time edit-elem))
|
||||||
|
|
||||||
|
(= c mousedown)
|
||||||
|
(do (dom/-activate! (.-selectedTarget event))
|
||||||
|
(let [now (.now js/Date)]
|
||||||
|
(go (<! (timeout 500))
|
||||||
|
(>! edit {:mousedown-time now :elem (.-selectedTarget event)}))
|
||||||
|
(recur now edit-elem)))
|
||||||
|
|
||||||
|
(= c mouseup)
|
||||||
|
(do (dom/-deactivate! (.-selectedTarget event))
|
||||||
|
(>! out (parse-completed-event event))
|
||||||
|
(recur nil edit-elem))
|
||||||
|
|
||||||
|
(= c mouseleave)
|
||||||
|
(do (dom/-deactivate! (.-selectedTarget event))
|
||||||
|
(recur nil edit-elem))
|
||||||
|
|
||||||
|
(= c edit)
|
||||||
|
(if (and mousedown-time (= (:mousedown-time event) mousedown-time))
|
||||||
|
(do (dom/-set-editing! (:elem event))
|
||||||
|
(recur nil (:elem event)))
|
||||||
|
(recur nil edit-elem))
|
||||||
|
|
||||||
|
:else (recur mousedown-time edit-elem))))))
|
||||||
|
out))
|
||||||
|
|
||||||
(defn init []
|
(defn init []
|
||||||
(render-body)
|
(dom/render-body)
|
||||||
(watch-for-state-changes)
|
(re-render-when-state-changes)
|
||||||
(push-outgoing-events))
|
(a/copy outgoing-events (event-loop)))
|
||||||
|
|
|
@ -22,8 +22,8 @@
|
||||||
(go (>! outgoing-events grub-event))))))
|
(go (>! outgoing-events grub-event))))))
|
||||||
|
|
||||||
(defn connect-to-server []
|
(defn connect-to-server []
|
||||||
(let [full-url (str "ws://" (.-host (.-location js/document)) "/ws")]
|
(let [server-url (str "ws://" (.-host (.-location js/document)) "/ws")]
|
||||||
(reset! websocket* (js/WebSocket. full-url))
|
(reset! websocket* (js/WebSocket. server-url))
|
||||||
(aset @websocket* "onopen" (fn [event] (log "Connected:" event)))
|
(aset @websocket* "onopen" (fn [event] (log "Connected:" event)))
|
||||||
(aset @websocket* "onclose" (fn [event] (log "Connection closed:" event)))
|
(aset @websocket* "onclose" (fn [event] (log "Connection closed:" event)))
|
||||||
(aset @websocket* "onerror" (fn [event] (log "Connection error:" event)))
|
(aset @websocket* "onerror" (fn [event] (log "Connection error:" event)))
|
||||||
|
|
Loading…
Add table
Reference in a new issue