table stash : { Todos : serialized (list {Title : string, Completed : bool}) }
val enter = 13
val escape = 27
fun mapX [a] (f : (unit -> list a) -> a -> xbody) (ls : list a) : xbody =
let
fun mapX' ls acc =
case ls of
[] => <xml/>
| x :: ls => <xml>{f (fn () => List.revAppend acc ls) x}{mapX' ls (x :: acc)}</xml>
in
mapX' ls []
end
fun newTodo r =
title <- source r.Title;
completed <- source r.Completed;
editing <- source False;
underMouse <- source False;
return {Title = title, Completed = completed, Editing = editing, UnderMouse = underMouse}
fun saver ls =
dml (DELETE FROM stash WHERE TRUE);
dml (INSERT INTO stash (Todos) VALUES ({[serialize ls]}))
fun saveLoop todos =
ts <- get todos;
ls <- List.mapM (fn r =>
title <- get r.Title;
completed <- get r.Completed;
return {Title = title, Completed = completed}) ts;
rpc (saver ls);
sleep 5000;
saveLoop todos
fun main () =
todos <- oneOrNoRows1 (SELECT * FROM stash);
todos <- (case todos of
None => source []
| Some r =>
ls <- List.mapM newTodo (deserialize r.Todos);
source ls);
mainInputId <- fresh;
mainInput <- source "";
toggleAllId <- fresh;
toggleAll <- source False;
filterMode <- source Template.All;
editingInputId <- fresh;
editingInput <- source "";
return (Template.document
{Onload = (giveFocus mainInputId; saveLoop todos),
Body = <xml>
{Template.mainInput {Id = mainInputId,
Source = mainInput,
Onkeydown = fn ev => if ev.KeyCode = enter then
title <- get mainInput;
set mainInput "";
title <- return (String.trim title);
if title = "" then
return ()
else
t <- newTodo {Title = title, Completed = False};
ts <- get todos;
set todos (List.append ts (t :: []))
else
return ()}}
<dyn signal={ts <- signal todos;
case ts of
[] => return <xml/>
| _ => return <xml>
{Template.mainSection
{ToggleAll = {Id = toggleAllId,
Source = toggleAll,
Onchange = (b <- get toggleAll;
ts <- get todos;
List.app (fn it => set it.Completed b) ts)},
Todos = mapX (fn del it => <xml>
<dyn signal={mode <- signal filterMode;
completed <- signal it.Completed;
editng <- signal it.Editing;
return (if (completed && mode = Template.Active)
|| (not completed && mode = Template.Completed) then
<xml/>
else if editng then
let
val activated =
text <- get editingInput;
text <- return (String.trim text);
if text = "" then
set todos (del ())
else
set it.Title text;
set it.Editing False
in
Template.editingTodo {Id = editingInputId,
Source = editingInput,
Onkeydown = fn ev =>
if ev.KeyCode = enter then
activated
else if ev.KeyCode = escape then
set it.Editing False
else
return (),
Onblur = activated}
end
else
Template.notEditingTodo {Onmouseover = set it.UnderMouse True,
Onmouseout = set it.UnderMouse False,
Ondblclick = (text <- get it.Title; set editingInput text; set it.Editing True),
Source = it.Completed,
Onchange = (ts <- get todos;
allComplete <- List.foldlM (fn it acc =>
completed <- get it.Completed;
return (completed && acc)) True ts;
set toggleAll allComplete),
Title = <xml><dyn signal={t <- signal it.Title; return (txt t)}/></xml>,
Destroy = <xml>
<dyn signal={um <- signal it.UnderMouse;
return (if um then
Template.destroyButton {Onclick = set todos (del ())}
else
<xml/>)}/>
</xml>})}/>
</xml>) ts}}
<dyn signal={(completed, remaining) <-
List.foldlM (fn it (completed, remaining) =>
comped <- signal it.Completed;
return (if comped then
(completed+1, remaining)
else
(completed, remaining+1)))
(0, 0) ts;
return (Template.filter {Source = filterMode,
Remaining = remaining,
Completed = completed,
ClearCompleted = (ts <- get todos;
ts' <- List.filterM (fn it =>
b <- get it.Completed;
return (not b)) ts;
set todos ts';
set toggleAll False)})}/>
</xml>}/>
</xml>})