sourceextensions/web/demos/todomvc.xtl
1⍝# TodoMVC on the web: the todos live in SQLite, the X_eTaL program is
2⍝# the server -- it routes each request, keeps the filter, counts what is
3⍝# left, and writes the page; forms post and are redirected back.
4⍝# Run: just demo web todomvc, then open http://127.0.0.1:8470/
5⍝# (the list is kept in work/todos.db; Quit ends the program)
6
7ʷᵇ⁼u̲se< "Web"
8"sq:" u̲se< "Sqlite"
9
10db ← "work/todos.db"
11made ← db sq:e̲xec "create table if not exists todos (id integer primary key, title text not null, done integer not null default 0)"
12
13⍝# each todo as a list item, written by SQL (the title escaped there)
14items ← "select coalesce(group_concat('<li class=' || case when done then 'done' else 'open' end || '><form method=post action=/toggle><input type=hidden name=id value=' || id || '><button class=tick title=toggle>' || case when done then '☑' else '☐' end || '</button></form><span>' || replace(replace(replace(title, '&', '&'), '<', '<'), '>', '>') || '</span><form method=post action=/delete><input type=hidden name=id value=' || id || '><button class=del title=delete>×</button></form></li>', ''), '') from (select * from todos"
15
16⍝# the filter a request asks for (?show=active or completed), as SQL
17ᵘf̲ilter ← { s →
18 s m̲atch "active" ? " where done = 0"
19 s m̲atch "completed" ? " where done = 1"
20 ""
21}
22
23⍝# "1 item left", "3 items left": counted in X_eTaL from the done column
24ᵘl̲eft ← { d →
25 n ← '+ r̲/ 0 = r̲avel d
26 n = 1 ? "1 item left"
27 (f̲ormat n) c̲at " items left"
28}
29
30⍝# a filter link, marked when chosen
31ᵘo̲n ← { s name →
32 s m̲atch name ? " class=on"
33 ((0 = t̲ally s) ∧ name m̲atch "all") ? " class=on"
34 ""
35}
36ᵘl̲ink ← { s name → "<a href='/?show=" c̲at name c̲at "'" c̲at (s ᵘo̲n name) c̲at ">" c̲at name c̲at "</a>" }
37ᵘl̲inks ← { s → (s ᵘl̲ink "all") c̲at " " c̲at (s ᵘl̲ink "active") c̲at " " c̲at s ᵘl̲ink "completed" }
38
39style ← "<style>body{background:#f5f5f5;font:15px system-ui,sans-serif;color:#333}main{max-width:520px;margin:2em auto}h1{color:#c0392b;font-weight:200;font-size:56px;text-align:center;margin:.2em}.box{background:#fff;box-shadow:0 2px 6px #0003}form.add input{width:100%;box-sizing:border-box;font-size:20px;padding:.6em;border:0;border-bottom:1px solid #eee}ul{list-style:none;margin:0;padding:0}li{display:flex;align-items:center;border-bottom:1px solid #eee;padding:.3em .5em}li span{flex:1;font-size:18px;padding:.3em}li.done span{text-decoration:line-through;color:#aaa}li form{margin:0}button{border:0;background:none;font-size:20px;cursor:pointer}.del{color:#c0392b}footer{display:flex;justify-content:space-between;padding:.6em;font-size:13px;color:#777}footer a{margin:0 .3em;color:#777}a.on{color:#c0392b;font-weight:bold}p.note{text-align:center;color:#999;font-size:12px}</style>"
40
41⍝# the whole page
42ᵘp̲age ← { s →
43 done ← db sq:n̲ums "select done from todos"
44 list ← r̲avel db sq:t̲exts items c̲at (ᵘf̲ilter s) c̲at " order by id)"
45 "<!doctype html><html><head><meta charset='utf-8'><title>todos -- X_eTaL on the web</title>" c̲at style c̲at "</head><body><main><h1>todos</h1><div class=box><form class=add method=post action=/add><input name=title placeholder='What needs to be done?' autofocus autocomplete=off></form><ul>" c̲at list c̲at "</ul><footer><span>" c̲at (ᵘl̲eft done) c̲at "</span><span>" c̲at (ᵘl̲inks s) c̲at "</span><form method=post action=/clear><button style='font-size:13px;color:#777'>Clear completed</button></form></footer></div><p class=note>Served by an X_eTaL program; kept in SQLite. <a href='/quit'>Quit</a></p></main></body></html>"
46}
47
48⍝# a change to the list, then back to the page (303 See Other)
49ᵘb̲ack ← { n →
50 h ← ʷᵇh̲eader! "Location: /"
51 303 ʷᵇr̲eply! ""
52}
53
54⍝# a new todo, unless its title is empty
55ᵘa̲dd ← { t →
56 0 = t̲ally t ? 0
57 db sq:e̲xec "insert into todos (title) values (" c̲at (sq:q̲uote t) c̲at ")"
58}
59
60⍝# one request; 0 once quit
61ᵘa̲nswer ← { k →
62 0 = k ? 0
63 r ← ʷᵇn̲ext! 60
64 r m̲atch "none" ? k
65 r m̲atch "GET /" ? (200 ʷᵇr̲eply! ᵘp̲age ʷᵇp̲aram "show") r̲ight k
66 r m̲atch "POST /add" ? (ᵘb̲ack ᵘa̲dd ʷᵇp̲aram "title") r̲ight k
67 r m̲atch "POST /toggle" ? (ᵘb̲ack db sq:e̲xec "update todos set done = 1 - done where id = " c̲at sq:q̲uote ʷᵇp̲aram "id") r̲ight k
68 r m̲atch "POST /delete" ? (ᵘb̲ack db sq:e̲xec "delete from todos where id = " c̲at sq:q̲uote ʷᵇp̲aram "id") r̲ight k
69 r m̲atch "POST /clear" ? (ᵘb̲ack db sq:e̲xec "delete from todos where done = 1") r̲ight k
70 r m̲atch "GET /quit" ? (200 ʷᵇr̲eply! "bye") r̲ight 0
71 (404 ʷᵇr̲eply! "no such page: " c̲at ʷᵇp̲ath @) r̲ight k
72}
73
74port ← ʷᵇs̲erve! 8470
75"serving http://127.0.0.1:" c̲at f̲ormat port
76last ← 100000 'ᵘa̲nswer p̲ower 1
77port ← ʷᵇs̲top! @
78"bye"