Reactive Todo List

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.

Example: Todo List
<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>

Try It!

How it works

State

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.

Adding items

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.

Rendering

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

Toggling done

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.

Removing items

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.