Skip to content

Lists and iteration ​

each repeats what's indented under it once per item of a list. It has no look of its own: the copies sit in the container around it, so each inside a row, grid, list or tabs makes one cell, row or tab per item.

sayshape
state todos=[{text: "Pack water", done: true}, {text: "Charge headlamp", done: false}]
card
  text "{count(todos, 'done')} of {len(todos)} packed" tone=muted
  each items=todos as=t
    row
      check "{t.text}" bind=t.done
      button "Remove" variant=secondary on=remove:t
  button "Add snacks" on="add:todos:{text: 'Snacks', done: false}"
Rendered by the SayShape engineOpen in playground
PropMeaning
itemsThe list: a state name, a field (o.lines) or an {expression}
asName for the current item inside (default item)
indexName for its position, from 0
keyA value unique to each item, like {t.id}, so focus stays with the item when the list changes (default: the position)

A loop variable exists only inside its each; using it outside is a warning.

Changing items ​

Inside each, bind= and actions can write to the current item: bind=t.done, toggle:t.done, inc:item.qty. This works only when items= names a list in state, not a computed one. The whole list is replaced with an updated copy, so the change event reports todos and your app can save it.

Changing the list ​

ActionEffect
add:todos:{text: 'Map', done: false}Adds an item to the end
remove:tRemoves the current item, inside each
remove:todos:0Removes the item at a position
clear:todosEmpties the list
set:todos:{filter(todos, 'done', false)}Replaces the list with a computed one
sayshape
state task="" nextId=3 todos=[{id: 1, text: "Book campsite", done: false}, {id: 2, text: "Check weather", done: true}]
card
  each items=todos as=t key={t.id}
    check "{t.text}" bind=t.done
  row align=bottom
    input "New task" bind=task placeholder="e.g. Buy fuel"
    button "Add" on="add:todos:{id: nextId, text: trim(task), done: false};inc:nextId;set:task:''" disabled={trim(task) == ''}
  button "Clear finished" variant=secondary on="set:todos:{filter(todos, 'done', false)}"
Rendered by the SayShape engineOpen in playground

Lists in lists ​

Put an each inside an each. Inner items can read outer ones:

sayshape
state orders=[{date: "May 2", lines: [{name: "Tent", price: 180}, {name: "Stove", price: 45}]}, {date: "May 9", lines: [{name: "Headlamp", price: 30}]}]
each items=orders as=o
  card
    text "Order of {o.date}" weight=semibold
    each items=o.lines as=line
      row
        text "{line.name}"
        text "{money(line.price)}" align=right
    text "Total {money(sum(o.lines, 'price'))}" align=right
Rendered by the SayShape engineOpen in playground

Change items of a filtered list ​

Filtering, sorting, searching and taking keep the items themselves, so an item of a list worked out from state still writes back to the list it came from. Tick a task in the filtered list and the task in tasks changes, along with every total worked out from tasks:

sayshape
state owner="All" tasks=[{text: "Mockups", owner: "Ana", done: true}, {text: "Pages", owner: "Ben", done: false}, {text: "Copy", owner: "Ana", done: false}]
derive shown={owner == 'All' ? tasks : filter(tasks, 'owner', owner)}
card
  segmented bind=owner options="All,Ana,Ben"
  text "{count(tasks, 'done')} of {len(tasks)} done"
  each items=shown as=t key={t.text}
    check "{t.text}" bind=t.done
Rendered by the SayShape engineOpen in playground

Items an expression builds, like each items={[{on: false}]}, aren't in state, so they can't be changed.

Hide items without a derived list ​

if= on the repeated element hides items in place:

sayshape
state hide=false gear=[{id: 1, name: "Tent", done: true}, {id: 2, name: "Stove", done: false}, {id: 3, name: "Headlamp", done: false}]
card
  switch "Hide packed" bind=hide
  each items=gear as=g key={g.id}
    check "{g.name}" bind=g.done if={!(hide && g.done)}
Rendered by the SayShape engineOpen in playground

Limits ​

At most 500 items per each, and 10,000 repeated elements per screen however lists nest. Past either, the rest are left out with a warning.

SayShape v0.1 is an early prototype under the MIT License. Status · GitHub