]>
Commit | Line | Data |
---|---|---|
1de09faf | 1 | Object subclass: Comment [ |
2 | | author text | | |
3 | ||
4 | author [ ^author ] | |
5 | text [ ^text ] | |
6 | ||
7 | author: aAuthor [ author := aAuthor ] | |
8 | text: aText [ text := aText ] | |
9 | ] | |
10 | ||
11 | Object subclass: Vote [ | |
12 | | author timestamp options | | |
13 | ||
14 | initialize [ | |
15 | options := OrderedCollection new. | |
16 | ] | |
17 | ||
18 | author [ ^author ] | |
19 | author: aAuthor [ author := aAuthor ] | |
20 | ||
21 | timestamp [ ^timestamp ] | |
22 | timestamp: aTimestamp [ timestamp := aTimestamp ] | |
23 | ||
24 | options [ ^options ] | |
25 | options: aOptions [ options := aOptions ] | |
26 | ] | |
27 | ||
28 | SandstoneDb.SDActiveRecord subclass: Poll [ | |
29 | | name comments expires options votes | | |
30 | ||
31 | initialize [ | |
32 | super initialize. | |
33 | comments := OrderedCollection new. | |
34 | options := OrderedCollection new. | |
35 | votes := OrderedCollection new. | |
36 | expires := DateTime now. | |
37 | ] | |
38 | ||
39 | name [ ^name ] | |
40 | comments [ ^comments ] | |
41 | options [ | |
42 | options ifNil: [ | |
43 | options := OrderedCollection new. | |
44 | self save. | |
45 | ]. | |
46 | ^options | |
47 | ] | |
48 | votes [ ^votes ] | |
49 | expires [ ^expires ] | |
50 | ||
51 | name: aName [ name := aName ] | |
52 | expires: aExpire [ expires := aExpire ] | |
53 | ||
54 | addComment: aComment [ comments add: aComment ] | |
55 | addOption: aOption [ options add: aOption ] | |
56 | addVote: aVote timestamp: aTimestamp [ aVote add: aTimestamp. votes add: aVote. ] | |
57 | expiresDate: aDate [ expires := DateTime date: aDate time: (expires asTime) ] | |
58 | expiresTime: aTime [ expires := DateTime date: (expires asDate) time: aTime ] | |
59 | ] | |
60 | Poll warmUp. | |
61 | ||
62 | DoodleComponent subclass: AddPollToSessionView [ | |
63 | ||
64 | authenticate: aToken [ | |
65 | | token | | |
66 | ||
67 | token := Token new. | |
68 | token deserialize: aToken. | |
69 | token validate. | |
70 | ||
71 | (token tokenvalue) ifNotNil: [ | |
72 | Seaside.WACurrentSession value addPoll: (token tokenvalue) | |
73 | ] | |
74 | ] | |
75 | ||
76 | renderContentOn: html [ | |
77 | html form class: 'pure-form'; | |
78 | with: [ | |
79 | html textInput attributeAt: 'placeholder' put: 'Access Token'; | |
80 | callback: [ :aToken | self authenticate: aToken ]. | |
81 | html submitButton value: 'Let me in!'; | |
82 | class: 'pure-button pure-button-primary' | |
83 | ||
84 | ] | |
85 | ] | |
86 | ] | |
87 | ||
88 | DoodleComponent subclass: NewCommentView [ | |
89 | renderContentOn: html [ | |
90 | | newcomment | | |
91 | ||
92 | newcomment := Comment new. | |
93 | ||
94 | html heading level: 1; | |
95 | with: 'Add a Comment'. | |
96 | ||
97 | html form class: 'pure-form pure-form-stacked'; | |
98 | with: [ | |
99 | html label for: 'author'; | |
100 | with: 'Author'. | |
101 | html textInput callback: [ :value | newcomment author: value ]; | |
102 | id: 'author'; | |
103 | attributeAt: 'placeholder' put: 'Author'. | |
104 | html label for: 'comment'; | |
105 | with: 'Comment'. | |
106 | html textArea callback: [ :value | newcomment text: value ]; | |
107 | id: 'comment'. | |
108 | html submitButton callback: [ self answer: newcomment ]; | |
109 | value: 'Create'; | |
110 | class: 'pure-button pure-button-primary'. | |
111 | ] | |
112 | ] | |
113 | ] | |
114 | ||
115 | DoodleComponent subclass: EditPollView [ | |
116 | | poll addAccess | | |
117 | ||
118 | initialize [ | |
119 | super initialize. | |
120 | addAccess := AddPollToSessionView new. | |
121 | ] | |
122 | ||
123 | children [ ^{ addAccess } ] | |
124 | ||
125 | poll [ | |
126 | ^poll | |
127 | ] | |
128 | ||
129 | poll: aPoll [ | |
130 | poll := aPoll | |
131 | ] | |
132 | ||
133 | pollId: aPollId [ | |
134 | poll := Poll atId: aPollId | |
135 | ] | |
136 | ||
137 | addContact [ | |
138 | | cmmnt | | |
139 | cmmnt := self call: (NewCommentView new). | |
140 | poll addComment: cmmnt. | |
141 | poll save. | |
142 | ] | |
143 | ||
144 | updateUrl: aUrl [ | |
145 | super updateUrl: aUrl. | |
146 | aUrl addParameter: 'pollId' value: (poll id). | |
147 | ] | |
148 | ||
149 | renderCommentOn: html comment: aComment [ | |
150 | html div class: 'poll-comment-box'; | |
151 | with: [ | |
152 | html heading level: 3; | |
153 | with: (aComment author). | |
154 | html paragraph: (aComment text) | |
155 | ] | |
156 | ] | |
157 | ||
158 | renderVoteResult: aVote on: html [ | |
159 | html tableRow: [ | |
160 | html tableData: [ html render: aVote author ]. | |
161 | aVote options do: [ :option | | |
162 | html tableData: [ | |
163 | (option = #ok) ifTrue: [ html span class: 'fa fa-check'. ]. | |
164 | (option = #meh) ifTrue: [ html span class: 'fa fa-question'. ]. | |
165 | (option = #nope) ifTrue: [ html span class: 'fa fa-times'. ]. | |
166 | ]. | |
167 | ]. | |
168 | html tableData: [ html render: aVote timestamp ]. | |
169 | ]. | |
170 | ] | |
171 | ||
172 | renderVoteOn: html [ | |
173 | | vote | | |
174 | vote := Vote new. | |
175 | vote initialize. | |
176 | ||
177 | html tableRow: [ | |
178 | html tableData: [ | |
179 | html textInput attributeAt: 'placeholder' put: 'Your Name'; | |
180 | callback: [ :value | vote author: value ]. | |
181 | ]. | |
182 | 1 to: (poll options size) do: [ :aIndex | | |
183 | vote options add: #meh. | |
184 | html tableData: [ | |
185 | | group | | |
186 | ||
187 | group := html radioGroup. | |
188 | group radioButton id: 'ok', aIndex asString; | |
189 | callback: [ :value | | |
190 | vote options add: #ok afterIndex: aIndex. | |
191 | vote options removeAtIndex: aIndex. | |
192 | ]. | |
193 | html label for: 'ok', aIndex asString; | |
194 | class: 'fa fa-check'. | |
195 | group radioButton id: 'meh', aIndex asString; | |
196 | selected: true; | |
197 | callback: [ :value | | |
198 | vote options add: #meh afterIndex: aIndex. | |
199 | vote options removeAtIndex: aIndex. | |
200 | ]. | |
201 | html label for: 'meh', aIndex asString; | |
202 | class: 'fa fa-question'. | |
203 | group radioButton id: 'no', aIndex asString; | |
204 | callback: [ :value | | |
205 | vote options add: #nope afterIndex: aIndex. | |
206 | vote options removeAtIndex: aIndex. | |
207 | ]. | |
208 | html label for: 'no', aIndex asString; | |
209 | class: 'fa fa-times'. | |
210 | ]. | |
211 | ]. | |
212 | html tableData: [ | |
213 | html submitButton callback: [ | |
214 | vote timestamp: (DateTime now). | |
215 | vote inspect. | |
216 | poll votes add: vote. | |
217 | poll save. ]; | |
218 | value: 'Vote'; | |
219 | class: 'pure-button pure-button-primary' | |
220 | ]. | |
221 | ]. | |
222 | ] | |
223 | ||
224 | renderOptionHeaderOn: html [ | |
225 | html tableHead: [ | |
226 | html tableRow: [ | |
227 | html tableHeading: ''. | |
228 | poll options do: [ :aOption | | |
229 | html tableHeading: aOption. | |
230 | ]. | |
231 | html tableHeading: ''. | |
232 | ]. | |
233 | ]. | |
234 | ] | |
235 | ||
236 | renderOptionsOn: html [ | |
237 | html form class: 'pure-form pure-form-aligned'; | |
238 | with: [ | |
239 | html table class: 'pure-table pure-table-striped'; | |
240 | with: [ | |
241 | self renderOptionHeaderOn: html. | |
242 | html tableBody: [ | |
243 | poll votes do: [ :vote | | |
244 | self renderVoteResult: vote on: html. | |
245 | ]. | |
246 | self renderVoteOn: html. | |
247 | ]. | |
248 | html tableFoot: [ | |
249 | html tableRow: [ | |
250 | html tableData: 'Summ:'. | |
251 | poll options do: [ :aOption | | |
252 | html tableData: 0. | |
253 | ]. | |
254 | html tableData: ''. | |
255 | ]. | |
256 | ]. | |
257 | ]. | |
258 | ]. | |
259 | ] | |
260 | ||
261 | renderContentOn: html [ | |
262 | | token | | |
263 | ||
264 | html heading | |
265 | level: 1; | |
266 | with: (poll name). | |
267 | (Seaside.WACurrentSession value hasAccess: (poll id)) ifTrue: [ | |
268 | html paragraph with: [ | |
269 | html render: 'The access token is: '. | |
270 | token := Token new. | |
271 | token tokenvalue: (poll id). | |
272 | token cryptography. | |
273 | html span class: 'access-token'; | |
274 | with: [ html render: (token serialize) ]. | |
275 | ]. | |
276 | self renderOptionsOn: html. | |
277 | poll comments do: [ :comment | | |
278 | self renderCommentOn: html comment: comment | |
279 | ]. | |
280 | html anchor | |
281 | callback: [ self addContact ]; | |
282 | with: 'Add Comment' | |
283 | ] ifFalse: [ | |
284 | html paragraph class: 'poll-error'; | |
285 | with: 'No access rights!'. | |
286 | html render: addAccess. | |
287 | ] | |
288 | ] | |
289 | ] | |
290 | ||
291 | DoodleComponent subclass: ListPollView [ | |
292 | editPoll: aPoll [ | |
293 | | epView | | |
294 | ||
295 | epView := EditPollView new. | |
296 | epView poll: aPoll. | |
297 | self call: epView | |
298 | ] | |
299 | ||
300 | renderPollRow: html poll: aPoll [ | |
301 | (aPoll expires) < (DateTime now) ifFalse: [ | |
302 | html tableRow: [ | |
303 | html tableData: [ | |
304 | html anchor callback: [ self editPoll: aPoll ]; | |
305 | with: (aPoll name) | |
306 | ]. | |
307 | html tableData: (aPoll createdOn). | |
308 | html tableData: (aPoll expires) | |
309 | ] | |
310 | ] | |
311 | ] | |
312 | ||
313 | renderContentOn: html [ | |
314 | html heading level: 1; | |
315 | with: 'Recent polls'. | |
316 | html table class: 'pure-table pure-table-striped'; | |
317 | with: [ | |
318 | html tableHead: [ | |
319 | html tableRow: [ | |
320 | html tableHeading: 'Poll Name'. | |
321 | html tableHeading: 'Created'. | |
322 | html tableHeading: 'Expires'. | |
323 | ] | |
324 | ]. | |
325 | html tableBody: [ Poll do: [ :poll | self renderPollRow: html poll: poll ] ] | |
326 | ]. | |
327 | ] | |
328 | ] | |
329 | ||
330 | DoodleComponent subclass: NewPollView [ | |
331 | | poll options | | |
332 | ||
333 | initialize [ | |
334 | super initialize. | |
335 | options := OrderedCollection new. | |
336 | options add: 'Option 1'. | |
337 | options add: 'Option 2'. | |
338 | ] | |
339 | ||
340 | states [ ^{ self } ] | |
341 | ||
342 | optionField: html [ | |
343 | 1 to: (options size) do: [ :aIndex | | |
344 | html div class: 'pure-control-group'; | |
345 | with: [ | |
346 | html textInput value: (options at: aIndex); | |
347 | callback: [ :value | | |
348 | options add: value afterIndex: aIndex. | |
349 | options removeAtIndex: aIndex. | |
350 | ] | |
351 | ]. | |
352 | ]. | |
353 | ] | |
354 | ||
355 | save: aPoll [ | |
356 | | view | | |
357 | ||
358 | options do: [ :aOption | aPoll addOption: aOption ]. | |
359 | aPoll save. | |
360 | view := EditPollView new. | |
361 | view poll: aPoll. | |
362 | Seaside.WACurrentSession value addPoll: (aPoll id). | |
363 | self call: view. | |
364 | ] | |
365 | ||
366 | renderContentOn: html [ | |
367 | | newpoll | | |
368 | newpoll := Poll new. | |
369 | ||
370 | html heading level: 1; | |
371 | with: 'Create a new Poll'. | |
372 | html form class: 'pure-form pure-form-aligned'; | |
373 | with: [ | |
374 | html div class: 'pure-control-group'; | |
375 | with: [ | |
376 | html label for: 'name'; | |
377 | with: 'Name'. | |
378 | html textInput callback: [ :value | newpoll name: value ]; | |
379 | id: 'name'. | |
380 | ]. | |
381 | html div class: 'pure-control-group'; | |
382 | with: [ | |
383 | html label for: 'expiration'; | |
384 | with: 'Expires'. | |
385 | html dateInput callback: [ :value | newpoll expiresDate: value ]; | |
386 | id: 'expiration'. | |
387 | html render: ' at '. | |
388 | html timeInput callback: [ :value | newpoll expiresTime: value ]. | |
389 | ]. | |
390 | html horizontalRule. | |
391 | self optionField: html. | |
392 | html div class: 'pure-control-group'; | |
393 | with: [ | |
394 | html anchor callback: [ options add: 'New Option' ]; | |
395 | with: 'More Options'. | |
396 | ]. | |
397 | html div class: 'pure-controls'; | |
398 | with: [ | |
399 | html submitButton callback: [ self save: newpoll ]; | |
400 | value: 'Create'; | |
401 | class: 'pure-button pure-button-primary' | |
402 | ] | |
403 | ] | |
404 | ] |