SolidX
ReferenceExtending SolidXFrontend Customization

List API

Reference for accessing and controlling SolidX list views programmatically from external components.

Overview

SolidX exposes a programmatic List View API so external components can control an active list page.

Typical callers now include:

  • Module-owned list buttons under admin-layout
  • Custom widgets under admin-layout
  • Bespoke route UIs under custom-layout

This API is exposed through:

  • listViewRegistry
  • getListView(listId)
  • getRegisteredListViewIds()
  • SolidListViewHandle

Source of Truth

Implementation references:

  • src/routes/pages/admin/core/ListPage.tsx
  • src/components/core/list/listViewRegistry.ts
  • src/components/core/list/SolidListView.tsx

How List Views Are Exposed

ListPage.tsx creates a list ID and registers the list handle on mount:

const listId = `page:${moduleName}:${modelName}:${menuItemId}:${menuItemName}:${actionId}:${actionName}`;
registerListView(listId, handle);

It unregisters on unmount:

unregisterListView(listId);

Important details:

  • modelName is camel-cased in ListPage
  • menuItemId, menuItemName, actionId, and actionName are part of the ID
  • Treat list IDs as fully-qualified keys and use exact matching

Registry API

import { getListView, getRegisteredListViewIds } from "@solidxai/core-ui";

Available functions:

  • getListView(listId)
  • getRegisteredListViewIds()
  • hasListView(listId)

SolidListViewHandle API

type SolidListViewHandle = {
  refresh: () => void;
  clearFilters: () => void;
  applyFilter: (filter: {
    custom_filter_predicate?: any;
    search_predicate?: any;
    saved_filter_predicate?: any;
    predefined_search_predicate?: any;
  }) => void;
  applySavedFilter: (name: string, variables?: Record<string, any>) => boolean;
  setPagination: (nextFirst: number, nextRows: number) => void;
  setSort: (nextMultiSortMeta: { field: string; order: 1 | -1 }[]) => void;
  setShowArchived: (value: boolean) => void;
  getState: () => any;
};

applySavedFilter looks up a savedFilters metadata entry by name, resolves its filterQueryJson tokens (see below), and applies it through the same state the built-in saved-filter dropdown uses — so a call to it shows up as a removable "Saved: <name>" pill in the global search bar, and getState().filterPredicates.saved_filter_name reflects it afterwards. It returns false (and logs an error) if the filter can't be found, or if any of its variables couldn't be resolved.

Saved Filters & Unbound Variables

A savedFilters metadata entry's filterQueryJson can contain string tokens shaped "$name" in place of a literal value:

{
  "name": "Tasks Created By Me",
  "modelUserKey": "task",
  "viewUserKey": "task-list-view",
  "filterQueryJson": {
    "$or": [{ "createdBy": { "$eq": "$activeUserId" } }]
  }
}

Two kinds of token:

  • $activeUserId - resolved automatically from the current session's user.id. No caller input needed; this is the only token the built-in saved-filter dropdown can apply on a plain click.
  • Any other $name (e.g. $priorityValues, $employeeId) - an unbound variable. It must be supplied via the variables argument to applySavedFilter/applySavedFilterByName, or the apply fails with a missing variables error.
{
  "name": "Filter By Priority",
  "modelUserKey": "task",
  "viewUserKey": "task-list-view",
  "filterQueryJson": {
    "$or": [{ "priority": { "$in": "$priorityValues" } }]
  }
}
listView.applySavedFilter("Filter By Priority", { priorityValues: ["High", "Urgent"] });

Why unbound filters don't appear in the plain saved-filter dropdown

The built-in saved-filter list (rendered from the search bar) deliberately excludes any saved filter with unbound variables - clicking it there would have no way to collect the variable's value, so the apply would just fail. This is intentional, not a bug to route around.

To make an unbound-variable filter selectable, build a small listHeaderAction component (see List View Buttons) that collects or resolves the value itself, then calls applySavedFilter directly:

import { getListView, getRegisteredListViewIds, getSession } from "@solidxai/core-ui";

const applyMyPriorityFilter = async (params: { moduleName: string; modelName: string }, priorityValues: string[]) => {
  const prefix = `page:${params.moduleName}:${params.modelName}:`;
  const listId = getRegisteredListViewIds().find((id) => id.startsWith(prefix));
  const listView = listId ? getListView(listId) : undefined;
  if (!listView) return;

  listView.applySavedFilter("Filter By Priority", { priorityValues });
};

For a value that needs an async lookup (e.g. the current user's linked employee record) rather than direct user input, resolve it with getSession() first:

const session = await getSession();
const employee = await resolveActiveEmployee(session); // your own lookup
if (employee?.employeeId) {
  listView.applySavedFilter("Tickets Assigned To Me", { employeeId: employee.employeeId });
}

See List View Events for the safe way to auto-apply one of these on list load, without needing a button click.

Typical External Usage

Pattern:

Read registered IDs.

Build the exact target listId from module, model, menu, and action context.

Resolve the handle via getListView.

Invoke handle APIs such as applyFilter or refresh.

Example:

import { getListView, getRegisteredListViewIds } from "@solidxai/core-ui";

const listIds = getRegisteredListViewIds();
const listId = "page:onboarding:applicationMaster:menu-123:Applications:action-456:open";
const listView = getListView(listId);

if (listView) {
  listView.applyFilter({
    custom_filter_predicate: {
      $and: [{ applicationNumber: { $in: matchingApplicationNumbers } }],
    },
  });
}

Filter Shape Notes

applyFilter(...) accepts predicate buckets used by the list search pipeline:

  • custom_filter_predicate
  • search_predicate
  • saved_filter_predicate
  • predefined_search_predicate

Troubleshooting

  • List handle is undefined -> list may not be mounted yet or the ID does not match exactly
  • No matching ID found -> inspect getRegisteredListViewIds() and verify every segment
  • Filter call has no effect -> verify predicate structure and target model field names
  • Wrong target list updated -> use exact listId matching only