A reactive todo list with add, done, and remove
A complete todo list: add items, mark them done, remove them, filter by
text. State is a plain array of {text, done} objects, rendered by a
<script type="text/hyperscript-template" live> template that re-renders whenever the array changes.
<div class="todo-app"
_="init set $todos to [{text:'Learn hyperscript', done:true},
{text:'Build something cool', done:false},
{text:'Ship it', done:false}]
set $search to ''">
<div class="todo-input">
<input type="text" placeholder="What needs doing?"
_="on keyup[key is 'Enter'] trigger add end
on add
halt unless my value is not empty
append {text: my value, done: false} to $todos
clear me" />
<button _="on click send add to the previous <input/>">Add</button>
</div>
<input type="search" placeholder="Filter..."
class="todo-filter"
_="bind me to $search" />
<script type="text/hyperscript-template" live>
<ul class="todo-list">
#for todo in $todos where the todo's text contains $search ignoring case
<li class="${'done' if todo is done}">
<label>
<input type="checkbox" ${'checked' if todo is done}
_="on click set the todo's done to my checked" />
<span>${todo.text}</span>
</label>
<button class="todo-remove" _="on click remove todo from $todos">x</button>
</li>
#else
<li class="empty">Nothing here.</li>
#end
</ul>
</script>
</div>
All state lives in two global variables initialized on the wrapper div:
init set $todos to [{text:'Learn hyperscript', done:true},
{text:'Build something cool', done:false},
{text:'Ship it', done:false}]
set $search to ''
$todos is a plain array of {text, done} objects. $search is the
current filter string, two-way bound to the search input via bind.
The text input listens for Enter and has a named add event that the
button can trigger:
on keyup[key is 'Enter'] trigger add
on add
halt unless my value is not empty
append {text: my value, done: false} to $todos
clear me
append pushes a new object onto $todos. The live template sees the
mutation and re-renders.
The <script type="text/hyperscript-template" live> block renders the
list. #for iterates $todos with a where filter:
#for todo in $todos where the todo's text contains $search ignoring case
<li class="${'done' if todo is done}">
...
</li>
#else
<li class="empty">Nothing here.</li>
#end
Each checkbox writes back to the todo object directly:
<input type="checkbox" _="on click set the todo's done to my checked" />
Note that here we have inner hyperscript on the generated input, and it is able to reference todo as a normal variable.
This is because hyperscript captures scopes/closures associated with looping in templates and makes the loop variables
available in generated scripts, making scripting with hyperscript templates natural.
The remove button uses remove with the captured object reference:
<button _="on click remove todo from $todos">x</button>
remove todo from $todos finds the object by reference in the array and
splices it out. The live template reactively re-renders after the splice.