aboutsummaryrefslogtreecommitdiffhomepage
path: root/demo/more/grid.ur
blob: 7e593791b25ffa9a07517d593f079f2eeb5ad456 (plain)
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
con colMeta' = fn (row :: Type) (t :: Type) =>
                  {Header : string,
                   Project : row -> transaction t,
                   Update : row -> t -> transaction row,
                   Display : t -> xbody,
                   Edit : t -> xbody,
                   Validate : t -> signal bool}

con colMeta = fn (row :: Type) (global_t :: (Type * Type)) =>
                 {Initialize : transaction global_t.1,
                  Handlers : global_t.1 -> colMeta' row global_t.2}                  

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)}
                 val cols : $(map (colMeta row) cols)

                 val folder : folder cols

                 con aggregates :: {Type}
                 val aggregates : $(map (aggregateMeta row) aggregates)
                 val aggFolder : folder aggregates
             end) = struct
    style tabl
    style tr
    style th
    style td
    style agg

    fun make (row : M.row) [t] (m : colMeta' M.row t) : transaction t = m.Project row

    fun makeAll cols row = @@Monad.exec [transaction] _ [map snd M.cols]
                               (map2 [fst] [colMeta M.row] [fn p :: (Type * Type) => transaction p.2]
                                     (fn [p] data meta => make row [_] (meta.Handlers data))
                                     [_] M.folder cols M.cols)
                               (@@Folder.mp [_] [_] M.folder)

    fun addRow cols rows row =
        rowS <- source row;
        cols <- makeAll cols row;
        colsS <- source cols;
        ud <- source False;
        Monad.ignore (Dlist.append rows {Row = rowS,
                                         Cols = colsS,
                                         Updating = ud})

    type grid = {Cols : $(map fst M.cols),
                 Rows : Dlist.dlist {Row : source M.row, Cols : source ($(map snd M.cols)), Updating : source bool}}
 
    val createMetas = Monad.mapR [colMeta M.row] [fst]
                           (fn [nm :: Name] [p :: (Type * Type)] meta => meta.Initialize)
                           [_] M.folder M.cols

    val grid =
        cols <- createMetas;
        rows <- Dlist.create;
        return {Cols = cols, Rows = rows}

    fun sync {Cols = cols, Rows = rows} =
        Dlist.clear rows;
        init <- rpc M.list;
        List.app (addRow cols rows) init

    fun render grid = <xml>
      <table class={tabl}>
        <tr class={tr}>
          <th/> <th/>
          {foldRX2 [fst] [colMeta M.row] [_]
                   (fn [nm :: Name] [p :: (Type * Type)] [rest :: {(Type * Type)}] [[nm] ~ rest]
                                    data (meta : colMeta M.row p) =>
                       <xml><th class={th}>{[(meta.Handlers data).Header]}</th></xml>)
                   [_] M.folder grid.Cols M.cols}
        </tr>

        {Dlist.render (fn {Row = rowS, Cols = colsS, Updating = ud} 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 [fst] [colMeta M.row] [snd] [fn _ => option string]
                                                         (fn [nm :: Name] [p :: (Type * Type)] [rest :: {(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 [fst] [colMeta M.row] [snd] [fn _ => M.row]
                                                           (fn [nm :: Name] [t :: (Type * Type)]
                                                                            [rest :: {(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 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 (foldRX3 [fst] [colMeta M.row] [snd] [_]
                                                             (fn [nm :: Name] [t :: (Type * Type)]
                                                                              [rest :: {(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) grid.Rows}

            <dyn signal={rows <- Dlist.foldl (fn row => 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>
                           <td/><td/>
                           {foldRX2 [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>}/>
          </table>
          
          <button value="New row" onclick={row <- rpc M.new;
                                           addRow grid.Cols grid.Rows row}/>
          <button value="Refresh" onclick={sync grid}/>
    </xml>
end