22 KiB
Dynamic Forms and Pagination - Generic Frontend Patterns
This document describes the generic, reusable patterns for dynamic form generation and pagination that apply across all entity types in the frontend. These patterns enable completely backend-driven UI generation with no hardcoding.
Table of Contents
- Overview
- Backend Metadata System
- Dynamic Form Generation
- Pagination System
- Search and Filtering
- Sorting
- Localization
- Implementation Patterns
Overview
The frontend uses a completely backend-driven approach for generating forms, tables, filters, and pagination controls. All field definitions, labels, validation rules, and UI structure come from the backend through the attribute definition system.
Key Principles
- No Hardcoding: Field names, labels, types, validation rules, and options are never hardcoded
- Backend-Driven: All UI components generated from backend metadata
- Entity-Agnostic: Same patterns work for workflows, users, mandates, files, prompts, automations, etc.
- Automatic Updates: When backend adds new fields, frontend automatically displays them without code changes
- Localization: All labels and options support multiple languages from backend
Applicable Entity Types
These patterns apply to any entity type that has:
- An attributes endpoint:
GET /api/attributes/{EntityType} - A list endpoint supporting pagination:
GET /api/{entities}/?pagination=... - CRUD operations: Create, Read, Update, Delete
Examples: ChatWorkflow, User, Mandate, FileItem, Prompt, AutomationDefinition, ChatMessage, ChatLog, etc.
Backend Metadata System
Attribute Definition Endpoint
Every entity type exposes an attributes endpoint that provides complete metadata:
GET /api/attributes/{EntityType}
Examples:
GET /api/attributes/ChatWorkflowGET /api/attributes/UserGET /api/attributes/MandateGET /api/attributes/FileItemGET /api/attributes/Prompt
Attribute Definition Structure
Each attribute returned from the endpoint contains:
name- Field name (e.g., "status", "name", "email", "createdAt")type- Field data type (e.g., "text", "select", "integer", "timestamp", "checkbox", "email", "url")label- Localized field label (object with language keys:{"en": "English Label", "fr": "French Label"})description- Field description text (for help text/tooltips)required- Boolean indicating if field is required for validationreadonly- Boolean indicating if field is read-only (cannot be edited)editable- Boolean indicating if field can be edited (inverse of readonly)visible- Boolean indicating if field should be displayed in UIoptions- Array of options for select fields (each option hasvalueand localizedlabel)
Response Format
The attributes endpoint returns:
{
"entityType": "ChatWorkflow",
"attributes": [
{
"name": "status",
"type": "select",
"label": {"en": "Status", "fr": "Statut"},
"description": "Current status of the workflow",
"required": false,
"readonly": false,
"editable": true,
"visible": true,
"options": [
{"value": "running", "label": {"en": "Running", "fr": "En cours"}},
{"value": "completed", "label": {"en": "Completed", "fr": "Terminé"}}
]
},
{
"name": "name",
"type": "text",
"label": {"en": "Name", "fr": "Nom"},
"description": "Name of the workflow",
"required": true,
"readonly": false,
"editable": true,
"visible": true
}
// ... more attributes
]
}
Dynamic Form Generation
Dynamic forms are generated entirely from backend attribute definitions. The same pattern works for create forms, edit forms, and inline editing.
Form Generation Process
-
Fetch Attribute Definitions
- Call
GET /api/attributes/{EntityType}when form component mounts - Store attribute definitions in component state
- Call
-
Filter Attributes
- For edit forms: Filter where
visible: trueANDeditable: true - For create forms: Filter where
visible: trueANDeditable: true(may exclude readonly fields) - For display-only: Filter where
visible: true
- For edit forms: Filter where
-
Generate Form Fields
- Iterate through filtered attributes
- Generate appropriate input component based on
type - Use
labelfor field labels (localized) - Use
descriptionfor help text/tooltips - Use
requiredto show required indicators
-
Handle Form Submission
- Validate form using attribute metadata
- Send form data to appropriate endpoint (POST for create, PUT for update)
- Handle success/error responses
Field Type to Input Component Mapping
Based on the type property, generate appropriate input components:
text→ Text input (single line)textarea→ Multi-line text areaselect→ Dropdown/select input with options fromoptionsarrayinteger→ Number input (integer only)floatornumber→ Number input (decimal allowed)timestamp→ Date/time pickerdate→ Date picker (date only)time→ Time picker (time only)checkbox→ Checkbox input (boolean)email→ Email input (with email validation)url→ URL input (with URL validation)password→ Password input (masked)file→ File upload input
Form Field Rendering Rules
For each form field:
-
Field Label
- Use
label[userLanguage]orlabel.enas fallback - Show required indicator (asterisk, etc.) if
required: true
- Use
-
Input Component
- Generate based on
typeproperty - For
selectfields: Populate options fromoptionsarray, use localized labels - For
integer/numberfields: Set appropriate min/max constraints if available - For
timestamp/date/timefields: Use appropriate date/time picker component
- Generate based on
-
Help Text
- Display
descriptionas help text or tooltip - Show below or next to input field
- Display
-
Validation
- Mark field as required if
required: true - Validate type (e.g., integer fields must be numbers)
- Validate select fields (value must be in options array)
- Validate email/url fields with appropriate regex patterns
- Mark field as required if
-
Disabled State
- Disable input if
readonly: trueoreditable: false - Show read-only indicator if applicable
- Disable input if
Form Validation
Before form submission:
-
Required Field Validation
- Check all fields where
required: truehave non-empty values - Show error messages for missing required fields
- Check all fields where
-
Type Validation
- Validate integer fields contain valid integers
- Validate number fields contain valid numbers
- Validate email fields contain valid email addresses
- Validate URL fields contain valid URLs
- Validate timestamp/date fields contain valid dates
-
Option Validation
- For select fields, ensure value exists in
optionsarray - Show error if invalid option selected
- For select fields, ensure value exists in
-
Custom Validation
- Apply any additional validation rules from backend metadata if available
Form Submission
On form submit:
-
Collect Form Data
- Gather values from all form fields
- Use attribute
nameas keys in form data object
-
Prepare Request
- For create:
POST /api/{entities}with form data - For update:
PUT /api/{entities}/{id}with form data - Include only changed fields (for updates) or all form data (for creates)
- For create:
-
Handle Response
- On success: Show success message, navigate or refresh as appropriate
- On error: Display validation errors from backend, highlight invalid fields
Pagination System
Pagination is a unified system that works across all list endpoints. It supports pagination, sorting, filtering, and general search in a consistent way.
Pagination Endpoint Pattern
All list endpoints support pagination through a query parameter:
GET /api/{entities}/?pagination={JSON_ENCODED_PAGINATION_PARAMS}
Examples:
GET /api/workflows/?pagination={"page":1,"pageSize":20,"sort":[],"filters":null}GET /api/users/?pagination={"page":1,"pageSize":20,"sort":[{"field":"name","direction":"asc"}],"filters":{"status":"active"}}GET /api/files/list?pagination={"page":2,"pageSize":50,"sort":[],"filters":{"search":"report"}}
Pagination Request Parameters
The pagination query parameter is a JSON-encoded object with the following structure:
PaginationParams Structure:
page- Current page number (1-based, minimum 1)pageSize- Number of items per page (minimum 1, maximum 1000)sort- Array of sort field configurations- Each sort field contains:
field- Field name to sort by (must match an attribute name)direction- Sort direction: "asc" or "desc"
- Each sort field contains:
filters- Filter criteria dictionary (see Search and Filtering)
Example:
{
"page": 1,
"pageSize": 20,
"sort": [
{"field": "lastActivity", "direction": "desc"},
{"field": "name", "direction": "asc"}
],
"filters": {
"search": "invoice",
"status": "running"
}
}
Pagination Response Structure
All paginated endpoints return data in this format:
{
"items": [
// Array of entity objects
],
"pagination": {
"currentPage": 1,
"pageSize": 20,
"totalItems": 45,
"totalPages": 3,
"sort": [
{"field": "lastActivity", "direction": "desc"}
],
"filters": {
"search": "invoice",
"status": "running"
}
}
}
PaginationMetadata Structure:
currentPage- Current page number (1-based)pageSize- Number of items per pagetotalItems- Total number of items across all pages (after filters applied)totalPages- Total number of pages (calculated from totalItems / pageSize)sort- Current sort configuration applied (array of SortField objects)filters- Current filters applied (mirrors request filters)
Pagination UI Components
Generate pagination controls using the pagination metadata from response:
-
Page Information Display
- Show "Page X of Y" (using
currentPageandtotalPages) - Show "Showing X-Y of Z items" (calculate from
currentPage,pageSize,totalItems)
- Show "Page X of Y" (using
-
Page Navigation Controls
- Previous page button (disabled if
currentPage === 1) - Next page button (disabled if
currentPage === totalPages) - Page number buttons (show current page and adjacent pages)
- First page button (if not on first page)
- Last page button (if not on last page)
- Previous page button (disabled if
-
Page Size Selector
- Dropdown to change
pageSize(common options: 10, 20, 50, 100) - When changed, reset to page 1 and refetch
- Dropdown to change
-
Page Change Handler
- When user clicks page number or navigation button:
- Update
pagein pagination parameters - Preserve all filters and sort settings
- Refetch data with updated pagination
- Update
- When user clicks page number or navigation button:
Non-Paginated Requests
If user wants all items without pagination:
- Omit
paginationparameter entirely:GET /api/{entities}/ - Backend returns all items (still wrapped in
PaginatedResponsewithpagination: null)
Search and Filtering
The pagination system supports both general search and field-specific filtering. Both are combined in the filters object.
General Search
General search searches across all text fields in the entity.
Implementation:
- Display a single search input box (not field-specific)
- When user types, update
filters.searchin pagination parameters - Debounce search input (wait 300-500ms after user stops typing before sending request)
- Reset to page 1 when search query changes
- Combine search with field-specific filters (both are in the
filtersobject)
Filter Structure:
{
"filters": {
"search": "invoice"
}
}
Field-Specific Filtering
Field-specific filters allow filtering by individual fields with various operators.
Filter Structure:
Simple equals filter:
{
"filters": {
"status": "running"
}
}
Filter with operator:
{
"filters": {
"name": {
"operator": "contains",
"value": "workflow"
}
}
}
Supported Operators:
equalsoreq- Exact matchcontains- Substring match (case-insensitive for strings)startsWith- String starts with (case-insensitive)endsWith- String ends with (case-insensitive)gt- Greater than (for numbers/dates)gte- Greater than or equallt- Less thanlte- Less than or equalin- Value in list (array of values)notIn- Value not in list (array of values)
Multiple Filters: All filters are combined with AND logic:
{
"filters": {
"search": "invoice",
"status": "running",
"currentRound": {
"operator": "gt",
"value": 0
}
}
}
Filter Control Generation
Generate filter controls dynamically from attribute definitions:
-
Fetch Attribute Definitions
- Call
GET /api/attributes/{EntityType}when filter component mounts - Filter attributes where
visible: trueto determine which filters to show
- Call
-
Generate Filter UI by Type
textfields → Text input filter (supports "contains", "equals", "startsWith", "endsWith" operators)selectfields → Dropdown filter with options fromoptionsarray (use localized labels)integerornumberfields → Number range filter (min/max inputs) or single number input with comparison operators (gt, gte, lt, lte)timestampordatefields → Date range picker or single date picker with comparison operatorscheckboxfields → Boolean toggle filter (true/false/any)
-
Filter Labels
- Use
labelproperty for filter labels (localized) - Display filter label next to filter control
- Use
-
Apply Filters
- When user applies filter, update
filters[fieldName]in pagination parameters - Support multiple filters simultaneously
- Reset to page 1 when filters change
- Refetch data with updated filters
- When user applies filter, update
-
Active Filter Indicators
- Display active filters as chips/badges showing field label and value
- Allow removing individual filters by removing them from
filtersobject - Show "Clear all filters" button when filters are active
Combined Search and Filters
Search and field filters work together:
- Both are stored in the
filtersobject - All filters are combined with AND logic
- When any filter changes, combine all and refetch
- Preserve all active filters when sorting or changing pages
Sorting
Sorting allows users to order list items by one or more fields.
Sort Configuration
Sorting is configured in the sort array of pagination parameters:
{
"sort": [
{"field": "lastActivity", "direction": "desc"},
{"field": "name", "direction": "asc"}
]
}
- Multiple sort fields create multi-level sorting (sorts by first field, then by second field for ties)
- Sort fields are applied in order (first field is primary sort, second is secondary sort, etc.)
Sort UI Implementation
-
Column Header Sorting
- Make column headers clickable for sortable columns
- Show sort indicator (arrow up/down) next to column header when sorted
- Clicking a column header:
- If not currently sorted by that column: Add sort field with "asc" direction
- If sorted ascending: Change to "desc"
- If sorted descending: Remove sort field (or change to "asc" if keeping)
- Update
sortarray in pagination parameters - Preserve all filters and search
- Refetch data with updated sort
-
Sort Indicator Display
- Show up arrow (↑) for ascending sort
- Show down arrow (↓) for descending sort
- Show both arrows when column is sortable but not currently sorted
- Hide sort indicators for non-sortable columns
-
Multi-Level Sort
- When user clicks a second column header, add it to the
sortarray - Display multiple sort indicators if multiple columns are sorted
- Allow removing individual sort fields
- When user clicks a second column header, add it to the
Sortable Columns
Determine which columns are sortable:
- All visible columns can potentially be sortable
- Backend handles sorting logic, so frontend can attempt to sort any field
- If backend doesn't support sorting a field, it will ignore that sort field
- Readonly fields may still be sortable (e.g., timestamps, IDs)
Localization
All labels and options support multiple languages from the backend.
Language Detection
-
User Language Preference
- Get user's preferred language from user settings
- Fall back to browser locale if user setting not available
- Default to "en" (English) if no language detected
-
Label Access Pattern
- Access localized labels from
labelobject:label[userLanguage] - Fall back to
label.enif label for current language is missing - For select options:
option.label[userLanguage]oroption.label.enas fallback
- Access localized labels from
Localized Components
Apply localization to:
-
Form Fields
- Field labels:
attribute.label[userLanguage] - Help text/descriptions:
attribute.description(may be localized if backend provides) - Select options:
option.label[userLanguage] - Validation error messages: Use localized messages
- Field labels:
-
Table Columns
- Column headers:
attribute.label[userLanguage] - Cell values for select fields:
option.label[userLanguage]
- Column headers:
-
Filter Controls
- Filter labels:
attribute.label[userLanguage] - Filter options:
option.label[userLanguage]
- Filter labels:
-
Pagination Controls
- "Page X of Y" text: Use localized strings
- "Showing X-Y of Z items" text: Use localized strings
- Button labels (Previous, Next, etc.): Use localized strings
Implementation Patterns
Pattern 1: Generic List Page Component
A reusable list page component that works for any entity type:
Component Structure:
- Accept
entityTypeas prop (e.g., "ChatWorkflow", "User", "Mandate") - Fetch attributes:
GET /api/attributes/{entityType} - Fetch list data:
GET /api/{entities}/?pagination=... - Generate table columns from attributes
- Generate filter controls from attributes
- Handle pagination, search, filtering, and sorting
- Handle row clicks (navigate to detail page)
State Management:
- Store attribute definitions
- Store pagination parameters (page, pageSize, sort, filters)
- Store pagination metadata from response
- Store list items
- Store loading/error states
User Interactions:
- Search input → Update
filters.search - Filter controls → Update
filters[fieldName] - Column header clicks → Update
sortarray - Page navigation → Update
page - Page size change → Update
pageSize, reset to page 1
Pattern 2: Generic Form Component
A reusable form component that works for any entity type:
Component Structure:
- Accept
entityTypeandmodeas props ("create" or "edit") - Accept
entityIdas prop (for edit mode) - Fetch attributes:
GET /api/attributes/{entityType} - Fetch entity data (for edit mode):
GET /api/{entities}/{id} - Generate form fields from attributes
- Handle form submission
- Handle validation
Form Field Generation:
- Filter attributes:
visible: trueANDeditable: true - Generate input components based on
type - Apply labels, descriptions, required indicators
- Handle validation
Form Submission:
- Validate form using attribute metadata
- For create:
POST /api/{entities}with form data - For update:
PUT /api/{entities}/{id}with form data - Handle success/error responses
Pattern 3: Generic Table Component
A reusable table component that works for any entity type:
Component Structure:
- Accept
attributesanditemsas props - Generate columns from attributes (filter
visible: true) - Render table rows with formatted cell values
- Handle column header clicks for sorting
- Handle row clicks for navigation
Cell Value Formatting:
- Use
typeproperty to determine formatting - Format select fields using options array
- Format timestamps as relative/absolute time
- Format numbers with separators
- Handle null/undefined values
Pattern 4: Generic Filter Component
A reusable filter component that works for any entity type:
Component Structure:
- Accept
attributesandfiltersas props - Generate filter controls from attributes (filter
visible: true) - Handle filter changes
- Display active filters as chips/badges
- Allow removing individual filters
Filter Control Generation:
- Generate appropriate filter UI based on
type - Use localized labels
- Support multiple operators for text/number fields
- Update filters object and trigger refetch
Key Implementation Guidelines
-
Never Hardcode
- Never hardcode field names, labels, types, or validation rules
- Always fetch attribute definitions from backend
- Use attribute metadata for all UI generation
-
Entity-Agnostic
- Same components work for all entity types
- Pass
entityTypeas parameter to determine endpoints - Use generic prop names (attributes, items, etc.)
-
State Management
- Keep pagination state separate from entity data
- Preserve filters and sort when changing pages
- Reset to page 1 when filters/search change
-
Error Handling
- Handle API errors gracefully
- Display validation errors from backend
- Show loading states during API calls
-
Performance
- Debounce search input (300-500ms)
- Cache attribute definitions (don't refetch on every render)
- Use pagination to limit data fetched
-
Accessibility
- Use semantic HTML for forms and tables
- Provide ARIA labels for form fields
- Ensure keyboard navigation works
- Support screen readers
Summary
This generic system enables:
- Complete Backend-Driven UI: All forms, tables, filters generated from backend metadata
- Zero Hardcoding: No field definitions, labels, or validation rules in frontend code
- Automatic Updates: New backend fields automatically appear in frontend
- Consistent UX: Same patterns across all entity types
- Localization: Multi-language support from backend metadata
- Reusable Components: Generic components work for all entity types
By following these patterns, the frontend becomes a thin presentation layer that dynamically renders UI based on backend metadata, ensuring consistency, maintainability, and automatic adaptation to backend changes.