docs/docs/en/runjs/resource/multi-record-resource.md
A collection-oriented Resource: requests return an array and support pagination, filtering, sorting, and CRUD operations. It is suitable for "multiple records" scenarios such as tables and lists. Unlike APIResource, MultiRecordResource specifies the resource name via setResourceName(), automatically constructs URLs like users:list and users:create, and includes built-in capabilities for pagination, filtering, and row selection.
Inheritance: FlowResource → APIResource → BaseRecordResource → MultiRecordResource.
Creation: ctx.makeResource('MultiRecordResource') or ctx.initResource('MultiRecordResource'). Before use, you must call setResourceName('collectionName') (e.g., 'users'). In RunJS, ctx.api is injected by the runtime environment.
| Scenario | Description |
|---|---|
| Table Blocks | Table and list blocks use MultiRecordResource by default, supporting pagination, filtering, and sorting. |
| JSBlock Lists | Load data from collections like users or orders in a JSBlock and perform custom rendering. |
| Bulk Operations | Use getSelectedRows() to get selected rows and destroySelectedRows() for bulk deletion. |
| Association Resources | Load associated collections using formats like users.tags, which requires setSourceId(parentRecordId). |
getData() returns an array of records, which is the data field from the list API response.getMeta() returns pagination and other metadata: page, pageSize, count, totalPage, etc.| Method | Description |
|---|---|
setResourceName(name) / getResourceName() | The resource name, e.g., 'users', 'users.tags' (association resource). |
setSourceId(id) / getSourceId() | The parent record ID for association resources (e.g., for users.tags, pass the primary key of the user). |
setDataSourceKey(key) / getDataSourceKey() | Data source identifier (used in multi-data source scenarios). |
| Method | Description |
|---|---|
setFilterByTk(tk) / getFilterByTk() | Filter by primary key (for single record get, etc.). |
setFilter(filter) / getFilter() / resetFilter() | Filter conditions, supporting operators like $eq, $ne, $in, etc. |
addFilterGroup(key, filter) / removeFilterGroup(key) | Filter groups (for combining multiple conditions). |
setFields(fields) / getFields() | Requested fields (whitelist). |
setSort(sort) / getSort() | Sorting, e.g., ['-createdAt'] for descending order by creation time. |
setAppends(appends) / getAppends() / addAppends / removeAppends | Association loading (e.g., ['user', 'tags']). |
| Method | Description |
|---|---|
setPage(page) / getPage() | Current page (starting from 1). |
setPageSize(size) / getPageSize() | Number of items per page, default is 20. |
getTotalPage() | Total number of pages. |
getCount() | Total number of records (from server-side meta). |
next() / previous() / goto(page) | Change page and trigger refresh. |
| Method | Description |
|---|---|
setSelectedRows(rows) / getSelectedRows() | Currently selected row data, used for bulk deletion and other operations. |
| Method | Description |
|---|---|
refresh() | Requests the list with current parameters, updates getData() and pagination meta, and triggers the 'refresh' event. |
get(filterByTk) | Requests a single record and returns it (does not write to getData). |
create(data, options?) | Creates a record. Optional { refresh: false } prevents automatic refresh. Triggers 'saved'. |
update(filterByTk, data, options?) | Updates a record by its primary key. |
destroy(target) | Deletes records. target can be a primary key, a row object, or an array of primary keys/row objects (bulk delete). |
destroySelectedRows() | Deletes currently selected rows (throws an error if none are selected). |
setItem(index, item) | Replaces a specific row of data locally (does not initiate a request). |
runAction(actionName, options) | Calls any resource action (e.g., custom actions). |
| Method | Description |
|---|---|
setRefreshAction(name) | The action called during refresh, default is 'list'. |
setCreateActionOptions(options) / setUpdateActionOptions(options) | Request configuration for create/update. |
on('refresh', fn) / on('saved', fn) | Triggered after refresh completion or after saving. |
ctx.initResource('MultiRecordResource');
ctx.resource.setResourceName('users');
ctx.resource.setPageSize(20);
await ctx.resource.refresh();
const rows = ctx.resource.getData();
const total = ctx.resource.getCount();
ctx.resource.setResourceName('users');
ctx.resource.setFilter({ status: 'active' });
ctx.resource.setSort(['-createdAt']);
ctx.resource.setFields(['id', 'nickname', 'email']);
await ctx.resource.refresh();
ctx.resource.setResourceName('orders');
ctx.resource.setAppends(['user', 'items']);
await ctx.resource.refresh();
const orders = ctx.resource.getData();
await ctx.resource.create({ name: 'John Doe', email: '[email protected]' });
await ctx.resource.next();
await ctx.resource.previous();
await ctx.resource.goto(3);
const rows = ctx.resource?.getSelectedRows?.() || [];
if (rows.length === 0) {
ctx.message.warning('Please select data first');
return;
}
await ctx.resource.destroySelectedRows();
ctx.message.success(ctx.t('Deleted'));
ctx.resource?.on?.('refresh', () => {
const data = ctx.resource.getData();
ctx.render(<ul>{data?.map((r) => <li key={r.id}>{r.name}</li>)}</ul>);
});
await ctx.resource?.refresh?.();
const res = ctx.makeResource('MultiRecordResource');
res.setResourceName('users.roles');
res.setSourceId(ctx.record?.id);
await res.refresh();
const roles = res.getData();
setResourceName('collectionName') before use, otherwise the request URL cannot be constructed.parent.child (e.g., users.tags), you must call setSourceId(parentPrimaryKey) first.refresh() within the same event loop will only execute the last one to avoid redundant requests.data returned by the list API is an array of records, and getData() returns this array directly.