-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathcore.cljs
More file actions
155 lines (135 loc) · 5.66 KB
/
Copy pathcore.cljs
File metadata and controls
155 lines (135 loc) · 5.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
(ns todos-first.core
(:require
[cljs.core.async :as async
:refer [<! >! chan close! sliding-buffer put!]]
[jayq.core :refer [$ append ajax inner $deferred when done resolve pipe on] :as jq]
[jayq.util :refer [log]]
[crate.core :as crate]
[clojure.string :refer [join blank?]]
[todos-first.templates :refer [app-view]])
(:require-macros [cljs.core.async.macros :as m :refer [go alt!]]))
(def dlog (comp log prn-str))
(defn click-chan [input-chan selector ev-name]
(on ($ "body") :click selector {}
(fn [e] (jq/prevent e)
(let [data (-> e .-currentTarget $ .data
(js->clj :keywordize-keys true))]
(put! input-chan [ev-name data])))))
(defn fields-value-map [form-selector fields]
(into {} (map
(fn [fld]
[fld (jq/val ($ (str form-selector " input[name=" (name fld) "]")))] )
fields)))
(defn form-submit-chan [input-chan form-selector ev-name fields]
(on ($ "body") :submit form-selector {}
(fn [e]
(jq/prevent e)
(put! input-chan [ev-name (fields-value-map form-selector fields)]))))
(defn filter-chan [pred channel]
(go (loop []
(let [res (<! channel)]
(if (pred res) res (recur))))))
(defn filter-events [name-set channel]
(let [name-set (if (set? name-set) name-set #{name-set})]
(filter-chan #(and (pos? (count %)) (name-set (first %)))
channel)))
(defn render-page [state]
(-> ($ ".container")
(inner (crate/html (app-view state)))))
(defn validate-edit-form [fields]
(if (blank? (:name fields))
[[:name "can't be blank"]]
[]))
(defn validate-task-form [fields]
(if (blank? (:content fields))
[[:content "can't be blank"]]
[]))
(defn add-list [{:keys [data] :as state} list-name]
(let [new-sym (-> "todo-list-" gensym name keyword)
new-data (assoc-in data [:todo-lists new-sym]
{:name list-name :tasks {}})]
(assoc state
:data new-data
:focused-list new-sym)))
(defn add-list-form-app [input-chan state]
(go
(loop [form-state (assoc state
:mode :add-list
:new-list-form {})]
(render-page form-state)
(let [[event-name form-result] (<! (filter-events
#{:new-list-form-submit :cancel-new-list-form}
input-chan))]
(if (= event-name :cancel-new-list-form)
state
(let [errors (validate-edit-form form-result)]
(if (pos? (count errors))
(recur (assoc form-state :new-list-form {:name (form-result :name)
:errors errors}))
(add-list state (form-result :name)))))))))
(defn add-task [{:keys [focused-list] :as state} content]
(let [new-task-id (-> "task" gensym name keyword)
new-data (assoc-in (state :data) [:todo-lists
focused-list
:tasks
new-task-id
:content]
content)]
(assoc state :data new-data)))
(defn do-add-task [state form-vals]
(let [errors (validate-task-form form-vals)]
(if (pos? (count errors))
(assoc state :new-task-form { :content (:content form-vals)
:errors errors })
(-> state
(add-task (:content form-vals))
(dissoc :new-task-form)))))
(defn select-list [state {:keys [listId]}]
(assoc state :focused-list (keyword listId)))
(defn delete-task [{:keys [data] :as state} {:keys [taskId]}]
(assoc state :data
(update-in data
[:todo-lists
(:focused-list state)
:tasks] dissoc (keyword taskId))))
(defn complete-task [{:keys [data] :as state} {:keys [taskId]}]
(assoc state :data
(assoc-in data
[:todo-lists
(:focused-list state)
:tasks
(keyword taskId)
:completed] true )))
(defn delete-list [{:keys [data] :as state} {:keys [listId]}]
(let [updated-data (update-in data
[:todo-lists] dissoc (:focused-list state))]
(assoc state
:data updated-data
:focused-list (-> updated-data :todo-lists first first))))
(defn app-loop [start-state]
(let [input-chan (chan)]
(click-chan input-chan ".nav .new-list" :get-new-list)
(form-submit-chan input-chan ".new-list-form" :new-list-form-submit [:name])
(click-chan input-chan ".cancel-new-list" :cancel-new-list-form)
(form-submit-chan input-chan ".new-task-form" :new-task-form-submit [:content])
(click-chan input-chan ".list-nav a" :select-list)
(click-chan input-chan "a.delete-list" :delete-list)
(click-chan input-chan "a.delete-task" :delete-task)
(click-chan input-chan "a.complete-task" :complete-task)
(go
(loop [state start-state]
(log "loop top " (prn-str state))
(render-page state)
(let [[ev-name ev-data] (<! input-chan)]
(condp = ev-name
:get-new-list (recur (<! (add-list-form-app input-chan state)))
:new-task-form-submit (recur (do-add-task state ev-data))
:select-list (recur (select-list state ev-data))
:delete-list (recur (delete-list state ev-data))
:delete-task (recur (delete-task state ev-data))
:complete-task (recur (complete-task state ev-data))
(recur state)
))))))
(defn setup-page []
(app-loop {}))
(setup-page)