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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
|
con colMeta' = fn (row :: Type) (input :: Type) (filter :: Type) =>
{Header : string,
Project : row -> transaction input,
Update : row -> input -> transaction row,
Display : input -> xbody,
Edit : input -> xbody,
Validate : input -> signal bool,
CreateFilter : transaction filter,
DisplayFilter : filter -> xbody,
Filter : filter -> row -> signal bool,
Sort : option (row -> row -> bool)}
con colMeta = fn (row :: Type) (global :: Type, input :: Type, filter :: Type) =>
{Initialize : transaction global,
Handlers : global -> colMeta' row input filter}
con aggregateMeta = fn (row :: Type) (acc :: Type) =>
{Initial : acc,
Step : row -> acc -> acc,
Display : acc -> xbody}
functor Make(M : sig
type row
type key
val keyOf : row -> key
val list : transaction (list row)
val new : transaction row
val save : key -> row -> transaction unit
val delete : key -> transaction unit
con cols :: {(Type * Type * Type)}
val cols : $(map (colMeta row) cols)
val folder : folder cols
con aggregates :: {Type}
val aggregates : $(map (aggregateMeta row) aggregates)
val aggFolder : folder aggregates
val pageLength : option int
end) = struct
style tabl
style tr
style th
style td
style agg
fun make (row : M.row) [input] [filter] (m : colMeta' M.row input filter) : transaction input = m.Project row
fun makeAll cols row = @@Monad.exec [transaction] _ [map snd3 M.cols]
(@map2 [fst3] [colMeta M.row] [fn p => transaction (snd3 p)]
(fn [p] data meta => make row (meta.Handlers data))
M.folder cols M.cols)
(@@Folder.mp [_] [_] M.folder)
type listT = {Row : source M.row,
Cols : source ($(map snd3 M.cols)),
Updating : source bool,
Selected : source bool}
type grid = {Cols : $(map fst3 M.cols),
Rows : Dlist.dlist listT,
Selection : source bool,
Filters : $(map thd3 M.cols),
Sort : source (option (M.row -> M.row -> bool)),
Position : source int}
fun newRow cols row =
rowS <- source row;
cols <- makeAll cols row;
colsS <- source cols;
ud <- source False;
sd <- source False;
return {Row = rowS,
Cols = colsS,
Updating = ud,
Selected = sd}
fun addRow cols rows row =
r <- newRow cols row;
Monad.ignore (Dlist.append rows r)
val grid =
cols <- @Monad.mapR _ [colMeta M.row] [fst3]
(fn [nm :: Name] [p :: (Type * Type * Type)] meta => meta.Initialize)
M.folder M.cols;
filters <- @Monad.mapR2 _ [colMeta M.row] [fst3] [thd3]
(fn [nm :: Name] [p :: (Type * Type * Type)] meta state =>
(meta.Handlers state).CreateFilter)
M.folder M.cols cols;
rows <- Dlist.create;
sel <- source False;
sort <- source None;
pos <- source 0;
return {Cols = cols,
Rows = rows,
Selection = sel,
Filters = filters,
Sort = sort,
Position = pos}
fun sync {Cols = cols, Rows = rows, ...} =
Dlist.clear rows;
init <- rpc M.list;
rs <- List.mapM (newRow cols) init;
Dlist.replace rows rs
fun myFilter grid all =
row <- signal all.Row;
@foldR3 [colMeta M.row] [fst3] [thd3] [fn _ => M.row -> signal bool]
(fn [nm :: Name] [p :: (Type * Type * Type)]
[rest :: {(Type * Type * Type)}] [[nm] ~ rest]
meta state filter combinedFilter row =>
previous <- combinedFilter row;
this <- (meta.Handlers state).Filter filter row;
return (previous && this))
(fn _ => return True)
M.folder M.cols grid.Cols grid.Filters row
fun render (grid : grid) = <xml>
<table class={tabl}>
<tr class={tr}>
<th/> <th/> <th><button value="No sort" onclick={set grid.Sort None}/></th>
{@mapX2 [fst3] [colMeta M.row] [tr]
(fn [nm :: Name] [p :: (Type * Type * Type)] [rest :: {(Type * Type * Type)}] [[nm] ~ rest]
data (meta : colMeta M.row p) =>
<xml><th class={th}>
{case (meta.Handlers data).Sort of
None => txt (meta.Handlers data).Header
| sort => <xml><button value={(meta.Handlers data).Header}
onclick={set grid.Sort sort}/></xml>}
</th></xml>)
M.folder grid.Cols M.cols}
</tr>
{Dlist.render (fn {Row = rowS, Cols = colsS, Updating = ud, Selected = sd} pos =>
let
val delete =
Dlist.delete pos;
row <- get rowS;
rpc (M.delete (M.keyOf row))
val update = set ud True
val cancel =
set ud False;
row <- get rowS;
cols <- makeAll grid.Cols row;
set colsS cols
val save =
cols <- get colsS;
errors <- @Monad.foldR3 _ [fst3] [colMeta M.row] [snd3] [fn _ => option string]
(fn [nm :: Name] [p :: (Type * Type * Type)] [rest :: {(Type * Type * Type)}]
[[nm] ~ rest] data meta v errors =>
b <- current ((meta.Handlers data).Validate v);
return (if b then
errors
else
case errors of
None => Some ((meta.Handlers data).Header)
| Some s => Some ((meta.Handlers data).Header
^ ", " ^ s)))
None M.folder grid.Cols M.cols cols;
case errors of
Some s => alert ("Can't save because the following columns have invalid values:\n"
^ s)
| None =>
set ud False;
row <- get rowS;
row' <- @Monad.foldR3 _ [fst3] [colMeta M.row] [snd3] [fn _ => M.row]
(fn [nm :: Name] [t :: (Type * Type * Type)]
[rest :: {(Type * Type * Type)}]
[[nm] ~ rest] data meta v row' =>
(meta.Handlers data).Update row' v)
row M.folder grid.Cols M.cols cols;
rpc (M.save (M.keyOf row) row');
set rowS row';
cols <- makeAll grid.Cols row';
set colsS cols
in
<xml><tr class={tr}>
<td>
<dyn signal={b <- signal grid.Selection;
return (if b then
<xml><ccheckbox source={sd}/></xml>
else
<xml/>)}/>
</td>
<td>
<dyn signal={b <- signal ud;
return (if b then
<xml><button value="Save" onclick={save}/></xml>
else
<xml><button value="Update" onclick={update}/></xml>)}/>
</td>
<td><dyn signal={b <- signal ud;
return (if b then
<xml><button value="Cancel" onclick={cancel}/></xml>
else
<xml><button value="Delete" onclick={delete}/></xml>)}/>
</td>
<dyn signal={cols <- signal colsS;
return (@mapX3 [fst3] [colMeta M.row] [snd3] [_]
(fn [nm :: Name] [t :: (Type * Type * Type)]
[rest :: {(Type * Type * Type)}]
[[nm] ~ rest] data meta v =>
<xml><td class={td}>
<dyn signal={b <- signal ud;
return (if b then
(meta.Handlers data).Edit v
else
(meta.Handlers data).Display
v)}/>
<dyn signal={b <- signal ud;
if b then
valid <-
(meta.Handlers data).Validate v;
return (if valid then
<xml/>
else
<xml>!</xml>)
else
return <xml/>}/>
</td></xml>)
M.folder grid.Cols M.cols cols)}/>
</tr></xml>
end)
{StartPosition = case M.pageLength of
None => return None
| Some len =>
avail <- Dlist.numPassing (myFilter grid) grid.Rows;
pos <- signal grid.Position;
return (Some (if pos >= avail then
0
else
pos)),
MaxLength = return M.pageLength,
Filter = myFilter grid,
Sort = f <- signal grid.Sort;
return (Option.mp (fn f r1 r2 => r1 <- signal r1.Row;
r2 <- signal r2.Row;
return (f r1 r2)) f)}
grid.Rows}
<dyn signal={rows <- Dlist.foldl (fn row : listT =>
@Monad.mapR2 _ [aggregateMeta M.row] [id] [id]
(fn [nm :: Name] [t :: Type] meta acc =>
Monad.mp (fn v => meta.Step v acc)
(signal row.Row))
M.aggFolder M.aggregates)
(@mp [aggregateMeta M.row] [id]
(fn [t] meta => meta.Initial)
M.aggFolder M.aggregates) grid.Rows;
return <xml><tr>
<th colspan={3}>Aggregates</th>
{@mapX2 [aggregateMeta M.row] [id] [_]
(fn [nm :: Name] [t :: Type] [rest :: {Type}] [[nm] ~ rest] meta acc =>
<xml><td class={agg}>{meta.Display acc}</td></xml>)
M.aggFolder M.aggregates rows}
</tr></xml>}/>
<tr><th colspan={3}>Filters</th>
{@mapX3 [colMeta M.row] [fst3] [thd3] [_]
(fn [nm :: Name] [p :: (Type * Type * Type)] [rest :: {(Type * Type * Type)}] [[nm] ~ rest]
meta state filter => <xml><td>{(meta.Handlers state).DisplayFilter filter}</td></xml>)
M.folder M.cols grid.Cols grid.Filters}
</tr>
</table>
{case M.pageLength of
None => <xml/>
| Some plen => <xml>
<dyn signal={avail <- Dlist.numPassing (myFilter grid) grid.Rows;
return (if avail <= plen then
<xml/>
else
let
val numPages = avail / plen
val numPages = if numPages * plen < avail then
numPages + 1
else
numPages
fun pages n =
if n * plen >= avail then
<xml/>
else
<xml>
<dyn signal={pos <- signal grid.Position;
return (if n * plen = pos then
<xml><b>{[n + 1]}</b></xml>
else
<xml>
<button value={show (n + 1)}
onclick={set grid.Position
(n * plen)
}/></xml>)}/>
{if (n + 1) * plen >= avail then <xml/> else <xml>|</xml>}
{pages (n + 1)}
</xml>
in
<xml><p><b>Pages:</b> {pages 0}</p></xml>
end)}/>
</xml>}
<button value="New row" onclick={row <- rpc M.new;
addRow grid.Cols grid.Rows row}/>
<button value="Refresh" onclick={sync grid}/>
</xml>
fun showSelection grid = grid.Selection
fun selection grid = Dlist.foldl (fn {Row = rowS, Selected = sd, ...} ls =>
sd <- signal sd;
if sd then
row <- signal rowS;
return (row :: ls)
else
return ls) [] grid.Rows
end
|