# Repeater

```ts
import { Repeater } from 'cx/widgets';
```



Repeater renders its children for each record in a collection. Use `recordAlias` to specify an accessor for accessing record data within the repeater.

## Example

```tsx
import { createModel } from "cx/data";
import { Controller, expr } from "cx/ui";
import { Button, Checkbox, Repeater } from "cx/widgets";

interface Item {
  text: string;
  checked: boolean;
}

interface PageModel {
  items: Item[];
  $record: Item;
}

const m = createModel<PageModel>();

class PageController extends Controller {
  onInit() {
    this.reset();
  }
  reset() {
    this.store.set(m.items, [
      { text: "Learn CxJS basics", checked: true },
      { text: "Build a sample app", checked: false },
      { text: "Master data binding", checked: false },
    ]);
  }
}

export default (
  <div class="flex flex-col gap-4" controller={PageController}>
    <div class="flex flex-col gap-2">
      <Repeater records={m.items} recordAlias={m.$record}>
        <div class="flex items-center gap-2">
          <Checkbox value={m.$record.checked} text={m.$record.text} />
          <Button
            icon="close"
            mod="hollow"
            onClick={(e, { store }) => {
              store.delete(m.$record);
            }}
          />
        </div>
      </Repeater>
    </div>
    <div class="flex items-center gap-4">
      <div class="text-sm text-muted-foreground">
        Completed:{" "}
        <span
          text={expr(
            m.items,
            (items: Item[]) => items.filter((a) => a.checked).length,
          )}
        />{" "}
        of <span text={m.items.length} /> tasks
      </div>
      <Button
        onClick={(e, ins) => {
          ins.getControllerByType(PageController).reset();
        }}
      >
        Reset
      </Button>
    </div>
  </div>
);

```

## Typed Model

Add `$record` to your model interface to get type-safe access to record data:

```tsx
interface PageModel {
  items: Item[];
  $record: Item;
}

const m = createModel<PageModel>();
```

Then use `recordAlias={m.$record}` to bind the record accessor:

```tsx
<Repeater records={m.items} recordAlias={m.$record}>
  <Checkbox value={m.$record.checked} text={m.$record.text} />
</Repeater>
```

## Accessing the Record Store

Event handlers like `onClick` receive an instance object as the second argument. This instance contains a `store` that provides access to the record-specific data view. Use this to manipulate individual records:

```tsx
<Button
  onClick={(e, { store }) => {
    store.delete(m.$record);
  }}
/>
```

The `store.delete(m.$record)` call removes the current record from the parent array.

## Sorting and Filtering

Use `sortField` and `sortDirection` for sorting. For filtering, use `filterParams` with `onCreateFilter`:

```tsx
import { createModel } from "cx/data";
import { Controller, expr } from "cx/ui";
import { getSearchQueryPredicate } from "cx/util";
import { HighlightedSearchText, Repeater, TextField } from "cx/widgets";

interface Product {
  name: string;
  price: number;
}

interface PageModel {
  products: Product[];
  $record: Product;
  search: string;
}

const m = createModel<PageModel>();

class PageController extends Controller {
  onInit() {
    this.store.init(m.products, [
      { name: "Banana", price: 1.2 },
      { name: "Apple", price: 0.9 },
      { name: "Orange", price: 1.5 },
      { name: "Mango", price: 2.3 },
      { name: "Pineapple", price: 3.5 },
      { name: "Strawberry", price: 4.0 },
      { name: "Grapes", price: 2.8 },
      { name: "Watermelon", price: 5.0 },
    ]);
  }
}

export default (
  <div class="flex flex-col gap-4" controller={PageController}>
    <TextField value={m.search} placeholder="Filter by name..." />
    <Repeater
      records={m.products}
      recordAlias={m.$record}
      sortField="name"
      sortDirection="ASC"
      filterParams={{ search: m.search }}
      onCreateFilter={(params) => {
        let predicate = getSearchQueryPredicate(params.search);
        return (item: Product) => predicate(item.name);
      }}
    >
      <div class="text-sm">
        <HighlightedSearchText text={m.$record.name} query={m.search} />
        <span text={expr(m.$record.price, (price) => ` - $${price}`)} />
      </div>
    </Repeater>
  </div>
);

```

## Nested Repeaters

For nested repeaters, define separate record aliases in your model:

```tsx
import { createModel } from "cx/data";
import { Controller } from "cx/ui";
import { Repeater } from "cx/widgets";

interface Transaction {
  id: number;
  description: string;
  amount: number;
}

interface Account {
  name: string;
  transactions: Transaction[];
}

interface PageModel {
  accounts: Account[];
  $account: Account;
  $transaction: Transaction;
}

const m = createModel<PageModel>();

class PageController extends Controller {
  onInit() {
    this.store.init(m.accounts, [
      {
        name: "Checking",
        transactions: [
          { id: 1, description: "Groceries", amount: -85.5 },
          { id: 2, description: "Salary", amount: 3500 },
          { id: 3, description: "Electric bill", amount: -120 },
        ],
      },
      {
        name: "Savings",
        transactions: [
          { id: 4, description: "Transfer in", amount: 500 },
          { id: 5, description: "Interest", amount: 12.5 },
        ],
      },
    ]);
  }
}

export default (
  <div class="flex flex-col gap-4" controller={PageController}>
    <Repeater records={m.accounts} recordAlias={m.$account}>
      <div class="border rounded p-3">
        <div class="font-medium mb-2 leading-none" text={m.$account.name} />
        <Repeater
          records={m.$account.transactions}
          recordAlias={m.$transaction}
        >
          <div class="flex justify-between text-sm py-1 border-b last:border-b-0">
            <span text={m.$transaction.description} />
            <span text={m.$transaction.amount} />
          </div>
        </Repeater>
      </div>
    </Repeater>
  </div>
);

```

## Configuration

| Property               | Type                    | Description                                                                                                                                                                                |
| ---------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `records`              | `Prop<any[]>`           | An array of records to be displayed                                                                                                                                                        |
| `recordAlias`          | `AccessorChain`         | Alias used to expose record data. Defaults to `$record`.                                                                                                                                   |
| `indexAlias`           | `AccessorChain`         | Alias used to expose record index. Defaults to `$index`.                                                                                                                                   |
| `sortField`            | `StringProp`            | A binding used to store the name of the field used for sorting the collection. Available only if `sorters` are not used.                                                                   |
| `sortDirection`        | `Prop<"ASC" \| "DESC">` | A binding used to store the sort direction. Available only if `sorters` are not used. Possible values are `"ASC"` and `"DESC"`. Defaults to `"ASC"`.                                       |
| `sorters`              | `SortersProp`           | A binding used to store the sorting order list. This should be an array of objects with `field` and `direction` properties (equivalent to `sortField` and `sortDirection`).                |
| `sortOptions`          | `object`                | Options for data sorting. See [Intl.Collator options](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Collator) for more info.                            |
| `filterParams`         | `StructuredProp`        | Parameters that affect filtering                                                                                                                                                           |
| `onCreateFilter`       | `function`              | Callback to create a filter function for given filter params                                                                                                                               |
| `onTrackMappedRecords` | `function`              | Callback function to track and retrieve displayed records. Accepts new records as a first argument. If `onCreateFilter` is defined, filtered records can be retrieved using this callback. |
| `keyField`             | `string`                | Field used to get the unique identifier. Improves performance on sort operations.                                                                                                          |
| `cached`               | `boolean`               | Set to `true` to enable caching for improved performance on large datasets                                                                                                                 |