Skip to content

Documentation

entry-list Component

SSR-backed collection data table with URL-driven search, schema-aware smart filters, pagination, plugin columns, and connection-aware preloading.

7/15/2026
7 min read Edit on GitHub

File: src/components/collection-display/entry-list.svelte

Primary table for collection view mode. Data comes from +page.server.ts props; the component does not perform client-side collection fetching.

Platform: Built on the unified Smart Table (createSmartTable server mode) + Collection Filtering. Design-system tables share the same headless controller and pagination primitive.


Architecture

flowchart LR subgraph Server["+page.server.ts"] DB[(Database)] Cache[L1/L2 Cache] end subgraph EL["entry-list"] URL[URL query sync] SF[createSmartFilter] TH[Headers + DnD] TR[Rows + selection] TF[TableFilter toolbar] SFR[SmartFilterRow] PG[TablePagination] MB[EntryListMultiButton] TS[TranslationStatus] end subgraph Preload["Preloading"] PE[preloadEntry — SvelteKit] WC["/api/collections/warm-cache"] end DB --> Cache --> EL TR -->|hover| PE TR -->|idle batch| WC

Features (as implemented)

Feature Implementation
Server-driven data entries and pagination props from SSR; tableData = $derived(serverEntries)
URL state search, page, pageSize, filter_{field}, sort fields via goto()
Debounced search globalSearchValue synced to URL; resets to page 1 on new search
Smart column filters createSmartFilter maps widgets → text/select/date/numberRange/boolean; URL keys filter_{name}
Active filter badges Dismissible chips + clear-all; aria-live count for accessibility
Status facets statusFacets from getStatusFacetsSmartTableStatusFacets chips
Saved views SmartTableSavedViewsMenu — named filter/sort/layout presets per collection
Column resize Header drag handles via ColumnResizeHandle + layoutKey: entry-list:{id}
List metrics SSR listMetrics + SmartTableMetricsBadge when ?debug=table
Sortable columns Header click toggles ASC/DESC; synced to URL
Pagination TablePagination; page sizes via rowsPerPage URL param
Multi-select Shift+click range selection; select-all checkbox
Bulk actions Delegated to entry-list-multi-button.svelte
Archived view showDeleted toggles active vs archived entries
Column manager Show/hide columns; drag-and-drop reorder (svelte-dnd-action)
Density normal / compact via TableFilterentryListPaginationSettings.density
localStorage Per-collection entryListPaginationSettings_{collectionId} + Smart Table layout prefs

| Plugin columns | availablePlugins → dynamic headers → PluginComponent cells | | Translation UI | Embeds translation-status.svelte in filter toolbar | | Hover preload | preloadEntry() from @utils/navigation on row hover | | Idle warm-cache | First 5 visible rows → POST /api/collections/warm-cache (one ID per request) | | Connection-aware | Disables preload on slow-2g / 2g / saveData (Network Information API) | | Row virtualization | On pages with >25 rows, only visible rows + buffer (5 above/below) render as DOM elements; spacers maintain scroll height |


URL Parameters

Param Purpose
search Global text search (server-side)
page Current page number
pageSize Rows per page
filter_{fieldName} Per-column filter value (from createSmartFilter.toURLParams())
Sort fields Written by onSortChange (see entryListPaginationSettings.sorting)

Smart Filters (createSmartFilter)

Files:

  • src/components/collection-display/create-smart-filter.svelte.ts — reactive factory + pure helpers
  • src/components/collection-display/smart-filter-row.svelte — type-aware filter row + badges

Filter control types are derived from collection schema widgets (database-agnostic — no adapter imports):

Control type When used UI control
text Default scalar fields Floating search input
select status, Select/Radio widgets Native select with options
date Date widgets, createdAt / updatedAt <input type="date">
numberRange Number / Price / Currency / Rating Min–max inputs (min:max in URL)
boolean Checkbox / toggle widgets Yes / No select

Unsafe widgets (media, rich text, relations, groups, etc.) are excluded via safeForFiltering: false.

Client API

const smartFilter = createSmartFilter(() => collection.value);

smartFilter.setFilter("status", "publish");
smartFilter.clearFilter("status");
smartFilter.clearAll();

// URL deep-link params: { filter_status: "publish", filter_title: null, ... }
updateURL({ ...smartFilter.toURLParams(), page: "1" });

// Adapter-agnostic server payload (contains operator — adapters interpret)
smartFilter.toFilterQuery(); // { status: { contains: "publish" } }

Security & architecture (must stay enforced server-side)

Concern Responsibility
Query safety parseCollectionListQuery + whitelistFilterParams drop unknown field names before the DB. Client allowedFieldIds is UX-only.
RBAC / tenant Auth + permission hooks; CollectionService injects tenantId when multi-tenant is enabled.
Caching (L1/L2 SWR) getOrSetSWR with key collection:{id}:query:{hash}:… (60s fresh / 5m stale). Empty pages are not Bloom-negative-cached.
Prefix invalidation Mutations → cacheService.invalidateCollection(id) clears all filter/search/page variants.
Server hooks Authentication and authorization run before the collection page loader.
Important

Number ranges are stored as adapter-agnostic strings (min:max). Today the page loader maps every filter_* to { contains: value }. Backend operators (gte/lte) can be added later without changing the frontend encoding.

Shared modules

Module Role
@utils/collection-query-filters URL parse, schema whitelist, stable queryHash, cache key builder
create-smart-filter.svelte.ts Reactive filter UI state + control types from schema widgets
system/table/table-filter.svelte Toolbar (search, density, filter/column toggles)
ui/table/filter.svelte Generic table toolbar primitive (design system)
@utils/table-controller.svelte Shared selection/sort class (re-exported from collection-display)

Architecture docs


Props

interface EntryListProps {
  entries: Entry[];
  pagination: {
    currentPage: number;
    pageSize: number;
    totalItems: number;
    pagesCount: number;
  };
  contentLanguage: string;
  breadcrumb?: Array<{ name: string; path: string }>;
  collectionStats?: {
    _id: string;
    name: string;
    count: number;
    lastModified: string;
  } | null;
}

Keyboard Shortcuts

Shortcuts are implemented on entry-list-multi-button, not in entry-list.svelte directly:

Shortcut Action
Alt+N Create
Alt+P Publish selected
Alt+U Unpublish selected
Alt+D Draft selected
Alt+Del Delete selected

Row Virtualization

When the page size exceeds 25 rows (useRowVirtualization = $derived(pageSize > 25)), the component activates row virtualization to avoid rendering all rows into the DOM:

flowchart LR subgraph Data["tableData (all rows)"] T1[row 1] T2[row 2] TX[...] TN[row N] end subgraph Virtual["Virtual Slice"] TS[spacerTop] VB[visibleRows: start - 5 .. end + 5] SB[spacerBottom] end subgraph DOM["Rendered DOM"] ST["<tr style="height: {spacerTop}px">"] VR["<tr> entry X </tr>"] SB2["<tr style="height: {spacerBottom}px">"] end Data --> Virtual Virtual --> DOM
  • Row height: 44px per row (defined in src/utils/table-constants.ts)
  • Buffer: 5 rows rendered above and below the visible viewport to prevent blank flashes during scroll
  • Scroll container: max-h-[calc(100dvh)] with overflow-auto; scroll position tracked via onscroll handler
  • Index mapping: {@const realIndex = virtualStartIndex + idx} preserves selection state across virtual/non-virtual modes
  • content-visibility: auto: Applied to virtualized rows for additional browser-level rendering optimization

Key state variables:

Variable Type Purpose
virtualScrollTop $state Current scroll position in pixels
containerHeight $state Visible height of the scroll container
virtualStartIndex $derived First visible row index (minus buffer)
virtualEndIndex $derived Last visible row index (plus buffer)
visibleRows $derived tableData.slice(virtualStartIndex, virtualEndIndex) — rows to render
spacerTop / spacerBottom $derived Padding rows above/below the visible window
Tip

Virtualization is opt-in — it only activates when pageSize > 25. Below that threshold, the table renders all rows directly with no overhead from scroll tracking.

Scheduling Note

Bulk Schedule from MultiButton calls onSchedule(date) which sets a _scheduled timestamp payload on selected entries. The full schedule-modal (date/time/action picker) is opened from header-edit / right-sidebar / entry-actions.ts via showScheduleModal(), not from the bulk schedule stub.


Plugin Columns

Plugins register ui.columns in their definition. entry-list adds headers and renders cells with:

<PluginComponent
  pluginId={...}
  componentName={header.component}
  {...mapPluginProps(header.props, entry)}
/>

Zone for list-level plugin UI: dashboard slot list_actions (see plugin docs).


Related Documentation

componentsentry-listtablepaginationfilteringsmart-filter
Was this page helpful?