Recipe: lists
List shows rows of data in a scrolling column. It only makes views for the rows on screen and reuses them as you scroll, so a list of thousands of entries costs about the same as a list of twenty.
The easiest way to keep a list in step with your data is to put the data in a ReactiveArray<ListItem> and call bindItems. After that you never touch the list: push, remove or reorder the array and the rows follow, without losing the scroll position.
A list you can add to and remove from
ts
import { Button, Component, DEFAULT_STAGE, Label, List, ListItem, ReactiveArray, Ref, Screen } from "willowui";
class Quest {
title: string;
reward: int;
constructor(title: string, reward: int) {
this.title = title;
this.reward = reward;
}
}
const screen: Screen = new Screen();
const quests: ReactiveArray<ListItem> = new ReactiveArray<ListItem>();
quests.push(new ListItem(new Quest("Find the lantern", 50)));
quests.push(new ListItem(new Quest("Clear the cellar", 120)));
quests.push(new ListItem(new Quest("Deliver the letter", 20)));
const selected: Ref<int> = new Ref<int>(-1);
const list: List = new List("quests", 32.0);
list.setPosition(20.0, 20.0);
list.setSize(320.0, 300.0);
list.setSelectable(true);
// Each row: the data is the Quest inside the ListItem.
list.setRenderItem((index: int, data: any): Component => {
const q: Quest = data as Quest;
return new Label("row", q.title + " (" + q.reward + " gold)", "default", 15.0);
});
list.bindItems(quests);
list.bindSelected(selected);
screen.add(list);
const detail: Label = new Label("detail", "Pick a quest", "default", 14.0);
detail.setPosition(360.0, 20.0);
screen.add(detail);
list.setOnChange((): void => {
const item: any = list.selectedItem();
if (item != null) { detail.setText("Reward: " + (item as Quest).reward + " gold"); }
});
const add: Button = new Button("add", "Add quest", "default", 14.0);
add.setPosition(20.0, 340.0);
add.setOnClick((): void => {
quests.push(new ListItem(new Quest("New quest " + (quests.length + 1), 10)));
});
screen.add(add);
const remove: Button = new Button("remove", "Abandon", "default", 14.0);
remove.setPosition(160.0, 340.0);
remove.setOnClick((): void => {
const i: int = selected.peek();
if (i >= 0 && i < quests.length) {
quests.removeAt(i);
list.clearSelection();
}
});
screen.add(remove);
DEFAULT_STAGE.setScene(engine.root2d);
DEFAULT_STAGE.add(screen);
App.onFrame((dt: float): void => { DEFAULT_STAGE.update(dt); });Notes:
setRenderItemgets the row index and theListItem's data, and returns the row's widget. Without it, each row shows its data as text.selectedItem()returns that same data, ornullwhen nothing is selected.- With focus on the list, Up and Down move the selection.
selectNext(),selectPrevious(),selectFirst()andselectLast()do the same from code, and scroll the row into view. setItems([...])replaces everything at once and scrolls back to the top. Use it for lists you rebuild rather than edit.
A short list in XML
For a handful of rows, a template's ui:for is enough. Each row's on: handler can pass the row's item and index to your controller, so one method serves every row:
xml
<Panel layout="vbox" spacing="4">
<Button ui:for="quest in quests" bind:text="quest.title" on:click="abandon(quest, index)" />
</Panel>Clicking a row calls callMethod("abandon", [quest, index]). ui:for builds a full widget per row and rebuilds them all when the array changes, so switch to a List for long lists. Directives has a complete controller, and Bindings and events covers handler arguments.
Other list widgets
| Widget | Use it for |
|---|---|
TileList | A grid of tiles, such as an inventory or a level select. Same bindItems and setRenderItem. |
SortableList | A list the player reorders by dragging or with the keyboard. |
TreeList, SortableTreeList | Nested items that open and close. |
DataTable, TreeTable | Rows with columns, sortable by column. |
See Containers and Composites. In XML, <List> takes itemHeight, selected and size; give it its rows from code with bindItems, after finding it with screen.findByName (see Bindings and events).
See also
- Reactive state for
ReactiveArrayandRef.