diff --git a/src-cljs/grub_client/async-utils.cljs b/src-cljs/grub_client/async-utils.cljs new file mode 100644 index 0000000..6f196ec --- /dev/null +++ b/src-cljs/grub_client/async-utils.cljs @@ -0,0 +1,66 @@ +(ns grub-client.async-utils + (:require [cljs.core.async :as async :refer [! chan put! alts!]]) + (:require-macros [cljs.core.async.macros :as m :refer [go]] + [grub-client.macros :refer [go-loop]])) + +(defn put-all! [cs x] + (doseq [c cs] + (put! c x))) + +(defn fan-out [in cs-or-n] + (let [cs (if (number? cs-or-n) + (repeatedly cs-or-n chan) + cs-or-n)] + (go (loop [] + (let [x (! c x)))) + c)) + +(defn copy-chan + ([c] + (first (fan-out c 1))) + ([out c] + (first (fan-out c [out])))) + +(defn event-chan + ([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 (! c v)))) + c)) + +(defn do-chan! [f source] + (go-loop + (let [v (! chan close! timeout]]) - (:require-macros [dommy.macros :refer [deftemplate sel1 node]] - [cljs.core.async.macros :as m :refer [go alt! alts!]] + (:require [cljs.core.async :refer [! >!! chan close! timeout]] + [grub-client.async-utils + :refer [fan-in fan-out event-chan filter-chan do-chan!]] + [grub-client.view :as view]) + (:require-macros [cljs.core.async.macros :refer [go]] [grub-client.macros :refer [log go-loop]])) -(deftemplate grub-template [grub] - [:tr - [:td - [:div.checkbox [:label [:input {:type "checkbox"}] grub]]]]) - -(def add-grub-text - (node [:input.form-control {:type "text" :placeholder "2 grubs"}])) - -(def add-grub-btn - (node [:button.btn.btn-default {:type "button"} "Add"])) - -(deftemplate main-template [grubs] - [: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]] - [:table.table.table-condensed - [:tbody#grubList]]] - [:div.col-lg-4]]]) - -(defn render-body [] - (dommy/prepend! (sel1 :body) (main-template))) - -(defn push-new-grub [channel] - (let [new-grub (dommy/value add-grub-text)] - (dommy/set-value! add-grub-text "") - (go (>! channel new-grub)))) - -(defn put-grubs-from-clicks [channel] - (dommy/listen! add-grub-btn :click #(push-new-grub channel))) - -(defn put-grubs-if-enter-pressed [channel event] - (when (= (.-keyIdentifier event) "Enter") - (push-new-grub channel))) - -(defn put-grubs-from-enter [channel] - (dommy/listen! add-grub-text - :keyup - (partial put-grubs-if-enter-pressed channel))) - -(defn get-added-grubs [] - (let [out (chan)] - (put-grubs-from-clicks out) - (put-grubs-from-enter out) - out)) - -(defn append-new-grub [grub] - (dommy/append! (sel1 :#grubList) (grub-template grub))) - -(defn append-new-grubs [chan] - (go-loop (let [grub (! out grub)))) - out)) - (def websocket* (atom nil)) -(defn push-grubs-to-server [chan] - (let [websocket (js/WebSocket. "ws://localhost:3000/ws")] - (aset websocket "onmessage" (fn [event] - (let [grub (.-data event)] - (log "Received grub:" grub) - (append-new-grub grub)))) - (go-loop (let [grub (! out x)))) - out-channels)) +(defn get-local-added-grubs [] + (let [grubs (fan-in [(view/get-grubs-from-clicks) (view/get-grubs-from-enter)])] + (filter-chan #(not (empty? %)) grubs))) + +(defn push-grubs-to-server [in] + (do-chan! #(.send @websocket* %) in)) + +(defn get-remote-added-grubs [] + (let [out (chan)] + (aset @websocket* "onmessage" (fn [event] + (let [grub (.-data event)] + (log "Received grub:" grub) + (go (>! out grub))))) + out)) (defn add-new-grubs-as-they-come [] - (let [added-grubs (get-added-grubs) - filtered-grubs (filter-empty-grubs added-grubs) - out-channels (fan-out filtered-grubs 2)] - (append-new-grubs (first out-channels)) - (push-grubs-to-server (second out-channels)))) + (let [added-local (get-local-added-grubs) + [added-local' added-local''] (fan-out added-local 2) + added-remote (get-remote-added-grubs) + added (fan-in [added-local' added-remote])] + (do-chan! view/append-new-grub added) + (push-grubs-to-server added-local''))) (defn init [] - (render-body) + (view/render-body) + (connect-to-server) (add-new-grubs-as-they-come)) (init) diff --git a/src-cljs/grub_client/view.cljs b/src-cljs/grub_client/view.cljs new file mode 100644 index 0000000..82c4f7d --- /dev/null +++ b/src-cljs/grub_client/view.cljs @@ -0,0 +1,57 @@ +(ns grub-client.view + (:require [dommy.core :as dommy] + [cljs.core.async :refer [! chan]]) + (:require-macros [dommy.macros :refer [deftemplate sel1 node]] + [cljs.core.async.macros :refer [go]])) + +(deftemplate grub-template [grub] + [:tr + [:td + [:div.checkbox [:label [:input {:type "checkbox"}] grub]]]]) + +(def add-grub-text + (node [:input.form-control {:type "text" :placeholder "2 grubs"}])) + +(def add-grub-btn + (node [:button.btn.btn-default {:type "button"} "Add"])) + +(deftemplate main-template [grubs] + [: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]] + [:table.table.table-condensed + [:tbody#grubList]]] + [:div.col-lg-4]]]) + +(defn render-body [] + (dommy/prepend! (sel1 :body) (main-template))) + +(defn append-new-grub [grub] + (dommy/append! (sel1 :#grubList) (grub-template grub))) + +(defn push-new-grub [out] + (let [new-grub (dommy/value add-grub-text)] + (dommy/set-value! add-grub-text "") + (go (>! out new-grub)))) + +(defn get-grubs-from-clicks [] + (let [out (chan)] + (dommy/listen! add-grub-btn :click #(push-new-grub out)) + out)) + +(defn put-grubs-if-enter-pressed [out event] + (when (= (.-keyIdentifier event) "Enter") + (push-new-grub out))) + +(defn get-grubs-from-enter [] + (let [out (chan)] + (dommy/listen! add-grub-text + :keyup + (partial put-grubs-if-enter-pressed out)) + out))