Summary#
A page's query is sent in answers of about 2 MB of rows (How many rows a page is sent), so a list over a table bigger than that
holds the first answer — and looks complete. Two members of the list say otherwise: rows.HasMore is true while
rows exist past what the page holds, and rows.LoadMore() reads the next answer, from the row after the last one
held, and appends it. The list grows in place; every slot that reads it re-renders.
entity Reading {
[Required, MaxLength(40)] string Station;
DateTime TakenAt;
decimal Value;
security { allow read, create when IsAnonymous || IsAuthenticated; }
}
[Route("/readings")] [Render(CSR)] [AllowAnonymous]
component ReadingsPage() {
live var readings = Reading.OrderByDescending(r => r.TakenAt).ToList();
render {
Stack(gap: 1, p: 6) {
foreach (var r in readings) { Text($"{r.Station}: {r.Value}"); }
if (readings.HasMore) { Button("Load more", onPress: () => readings.LoadMore()); }
}
}
}Signature#
rows.HasMore // bool — the set holds rows past what this list holds
rows.LoadMore(); // read the next answer and append it (awaited in a body)
LoadMoreButton(rows); // the kit's offer: drawn only while rows.HasMore
DataGrid("…", rows: rows, …); // pages by itself — nothing to addDescription#
The kit's controls page by themselves#
DataGrid draws Load more under its rows whenever the rows it was handed hold more, so a table over a page's query
pages with nothing written. A list you draw yourself takes LoadMoreButton(rows) under it — the same offer, gone once
the last answer is in:
[Route("/stations")] [Render(CSR)] [AllowAnonymous]
component StationsPage() {
live var readings = Reading.OrderBy(r => r.Station).ToList();
render {
Stack(gap: 4, p: 6) {
DataGrid("Readings", rows: readings,
columns: [ new GridColumn<Reading> { Name = "Station", Label = "Station", Value = r => r.Station } ]);
Stack(gap: 1) {
foreach (var r in readings) { Text(r.Station); }
LoadMoreButton(readings, label: "More readings");
}
}
}
}⚠ A DataGrid's search, column filters and sort work on the rows it has loaded. To search the whole table, put the
search in the page's query (Reading.Where(r => r.Station.Contains(find))), which the database answers over every row.
Asked of the list, wherever it travels#
HasMore is a question about the value, not about how it was declared — which is why a control handed a page's
query as a plain T[] rows can ask it. A list that is not a window of anything answers false, and LoadMore() on it
does nothing: an array built in memory, a server function's answer, a list computed from a query
(readings.Where(r => r.Value > 0) — ask readings, not the list made from it), or a .Take(n) the page has filled.
That is the true answer for each: it is every row it will ever have.
In an action, it is awaited#
LoadMore() in an action body waits for the next answer, so the statement after it sees the rows it appended:
[Route("/readings/count")] [Render(CSR)] [AllowAnonymous]
component ReadingCount() {
live var readings = Reading.OrderBy(r => r.TakenAt).ToList();
int held = 0;
action More() {
readings.LoadMore();
held = readings.Count;
}
render {
Stack(gap: 2, p: 6) {
Text($"{held} loaded");
Button("Load the next readings", onPress: More);
}
}
}Where can it not be written?#
In a render slot, LoadMore() is refused: a slot re-runs whenever what it reads changes, so a read written there
would run on every render. Call it from a press. In code that runs on the server, both are refused: a function's
list is every row its query names — server code is never windowed — so there is nothing more to ask for. Bound a server
read with .Take(n) and .Skip(n).
How big is the next answer?#
As large as the answer budget allows — about 2 MB of rows, measured as they are sent — so a page that Selects the few
fields it shows (Select the fields a page shows) gets more rows a press. The next answer starts at the row after the last one
held, so paging never skips or repeats a row. When the set changes while a page holds it (a live list re-reads after
a commit), the re-read keeps every row already loaded.
See also#
- How many rows a page is sent — how many rows a page is sent, and what is never capped
- A list or table with no bound — what
osy lintsays about a list that does not page - A picker over every row — the SearchPicker — choosing one row of many: a search the database answers
- Select the fields a page shows — more rows an answer by reading fewer fields