# Client List Interaction

https://dev.wisecp.com/es/client-list-interaction

The rows your preset writes and the script that searches, sorts and pages them meet through a fixed set of attributes.

## Overview

A record list is served in one of two modes, and the theme markup is the same in both. The mode is decided by the row count, not by you.

- **Client mode**: Below the threshold every row arrives in the first answer and the container has no `data-ajax`. Search, filter, sort and paging all run in the browser, against the attributes on each row.
- **Server mode**: Above it the container carries `data-ajax` and the script asks for one page at a time. The totals come from the answer instead of being counted locally.

> **Attributes are not optional in either mode**
> 
> Client mode reads them to decide what a filter matches. Server mode still reads them for sorting and for the visual meters. A row that omits one is not rejected and no error appears. It stops matching the filter it belongs to.

## Prerequisites

- Your preset already returns the row in `$row["html"]`.
- Your view has the `.list-rows` container with its three text attributes.
- Your theme has a `list.js`; copying another theme's is the usual start.

## Structure

Three nodes matter to the script, and it finds them by class or role, never by position.

- **.list-rows**: The container. Carries the data address in server mode and the wording in both.
- **.list-item**: One row, written by your preset, carrying the attributes below.
- **[data-role="empty"]**: The no-match block. It stays in the container and the script shows it. The zero-records block is a different thing, and it replaces the whole list.

## Reference

### Row attributes

Your preset writes these on the row element. Each one has a single reader, so leaving one out disables exactly that feature.

| Attribute | Value | What stops working without it |
| --- | --- | --- |
| `data-status` | status key | Status filter and the default sort order |
| `data-group` | type key | Type filter; the value has to match the option the toolbar offers |
| `data-flag` | segment key | The tile row above the list |
| `data-name` | visible name | Search, which reads this first |
| `data-search` | extra words | Search on anything not in the name, such as a domain or a number |
| `data-price` | raw amount | Sorting by price |
| `data-due` | timestamp | Sorting by due date and the remaining-term meter |
| `data-start` | timestamp | Sorting by newest and the term meter, which needs both ends |

Timestamps are seconds, not formatted dates: the script compares them as numbers and a formatted value reads as zero.

### Container attributes

- **data-ajax**: Written by the view from the table's own address, and present only in server mode. Its absence is the signal for client mode, so never write a placeholder value.
- **data-noun**: The key inside the script, in English, never shown to anyone.
- **data-txt-count · data-txt-nores · data-txt-noun**: The counter sentence, the no-match sentence and the word for the record. The first two hold the placeholders below.

The sentences are filled in, not concatenated, so the wording stays in the translator's hands:

- **_START_ · _END_ · _TOTAL_ · _NOUN_**: First row on the page, last row on the page, the filtered total, and the word from `data-txt-noun`. Every one of them may appear more than once.

### The server answer

In server mode the script asks the table's own address and expects the shape the panel uses:

```json
{
  "type": "partial",
  "total": 240,
  "total_filter": 18,
  "body": "<article class=\"list-item\" ...>...</article>..."
}
```

The request carries `page`, `perPage`, `search`, `filter[type]`, `filter[status]`, `order` and `direction`. The component builds all of them. A filter you add to the toolbar reaches the query only when the controller declared it.

## Example

```php
$row["html"] = '<article class="list-item"'
    . ' data-status="' . $status . '"'
    . ' data-group="'  . $type . '"'
    . ' data-flag="'   . $segment . '"'
    . ' data-name="'   . htmlspecialchars($name) . '"'
    . ' data-search="' . htmlspecialchars($domain . ' ' . $service["id"]) . '"'
    . ' data-price="'  . $amount . '"'
    . ' data-due="'    . strtotime($service["duedate"]) . '"'
    . ' data-start="'  . strtotime($service["cdate"]) . '">'
    . $body
    . '</article>';
```

## Pitfalls

> **A filter value has two ends**
> 
> The option in the toolbar and the attribute on the row have to hold the same value. When they drift apart the filter selects nothing. An empty result is a legitimate answer, so nobody is told that a mistake happened.

> **The two empty blocks are different**
> 
> The no-match block lives inside the container and the script shows it. The zero-records block replaces the toolbar, the list and the pager together, and the view decides that before the script ever starts. Writing only one leaves the visitor with an empty page and no way out.

> **Typing cancels the previous request**
> 
> In server mode each keystroke supersedes the one before it, and the older request is dropped on purpose. Do not add your own timer on top: two of them make the list flicker between answers.

> **Formatted values break sorting**
> 
> Dates go in as seconds and amounts as raw numbers. A value with a currency symbol or a thousands separator reads as zero and sinks to the bottom of every sort.

## Related Articles

- [Tables and Record Lists](https://dev.wisecp.com/en/tables-and-record-lists)
- [The Client Area](https://dev.wisecp.com/en/the-client-area)
- [Theme Translations](https://dev.wisecp.com/en/translating-a-theme)
