gateway/docs/frontend-documentation/dynamic-forms-and-pagination.md

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

  1. Overview
  2. Backend Metadata System
  3. Dynamic Form Generation
  4. Pagination System
  5. Search and Filtering
  6. Sorting
  7. Localization
  8. 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/ChatWorkflow
  • GET /api/attributes/User
  • GET /api/attributes/Mandate
  • GET /api/attributes/FileItem
  • GET /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 validation
  • readonly - 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 UI
  • options - Array of options for select fields (each option has value and localized label)

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

  1. Fetch Attribute Definitions

    • Call GET /api/attributes/{EntityType} when form component mounts
    • Store attribute definitions in component state
  2. Filter Attributes

    • For edit forms: Filter where visible: true AND editable: true
    • For create forms: Filter where visible: true AND editable: true (may exclude readonly fields)
    • For display-only: Filter where visible: true
  3. Generate Form Fields

    • Iterate through filtered attributes
    • Generate appropriate input component based on type
    • Use label for field labels (localized)
    • Use description for help text/tooltips
    • Use required to show required indicators
  4. 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 area
  • select → Dropdown/select input with options from options array
  • integer → Number input (integer only)
  • float or number → Number input (decimal allowed)
  • timestamp → Date/time picker
  • date → 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:

  1. Field Label

    • Use label[userLanguage] or label.en as fallback
    • Show required indicator (asterisk, etc.) if required: true
  2. Input Component

    • Generate based on type property
    • For select fields: Populate options from options array, use localized labels
    • For integer/number fields: Set appropriate min/max constraints if available
    • For timestamp/date/time fields: Use appropriate date/time picker component
  3. Help Text

    • Display description as help text or tooltip
    • Show below or next to input field
  4. 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
  5. Disabled State

    • Disable input if readonly: true or editable: false
    • Show read-only indicator if applicable

Form Validation

Before form submission:

  1. Required Field Validation

    • Check all fields where required: true have non-empty values
    • Show error messages for missing required fields
  2. 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
  3. Option Validation

    • For select fields, ensure value exists in options array
    • Show error if invalid option selected
  4. Custom Validation

    • Apply any additional validation rules from backend metadata if available

Form Submission

On form submit:

  1. Collect Form Data

    • Gather values from all form fields
    • Use attribute name as keys in form data object
  2. 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)
  3. 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"
  • 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 page
  • totalItems - 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:

  1. Page Information Display

    • Show "Page X of Y" (using currentPage and totalPages)
    • Show "Showing X-Y of Z items" (calculate from currentPage, pageSize, totalItems)
  2. 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)
  3. Page Size Selector

    • Dropdown to change pageSize (common options: 10, 20, 50, 100)
    • When changed, reset to page 1 and refetch
  4. Page Change Handler

    • When user clicks page number or navigation button:
      • Update page in pagination parameters
      • Preserve all filters and sort settings
      • Refetch data with updated pagination

Non-Paginated Requests

If user wants all items without pagination:

  • Omit pagination parameter entirely: GET /api/{entities}/
  • Backend returns all items (still wrapped in PaginatedResponse with pagination: null)

Search and Filtering

The pagination system supports both general search and field-specific filtering. Both are combined in the filters object.

General search searches across all text fields in the entity.

Implementation:

  1. Display a single search input box (not field-specific)
  2. When user types, update filters.search in pagination parameters
  3. Debounce search input (wait 300-500ms after user stops typing before sending request)
  4. Reset to page 1 when search query changes
  5. Combine search with field-specific filters (both are in the filters object)

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:

  • equals or eq - Exact match
  • contains - 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 equal
  • lt - Less than
  • lte - Less than or equal
  • in - 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:

  1. Fetch Attribute Definitions

    • Call GET /api/attributes/{EntityType} when filter component mounts
    • Filter attributes where visible: true to determine which filters to show
  2. Generate Filter UI by Type

    • text fields → Text input filter (supports "contains", "equals", "startsWith", "endsWith" operators)
    • select fields → Dropdown filter with options from options array (use localized labels)
    • integer or number fields → Number range filter (min/max inputs) or single number input with comparison operators (gt, gte, lt, lte)
    • timestamp or date fields → Date range picker or single date picker with comparison operators
    • checkbox fields → Boolean toggle filter (true/false/any)
  3. Filter Labels

    • Use label property for filter labels (localized)
    • Display filter label next to filter control
  4. 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
  5. Active Filter Indicators

    • Display active filters as chips/badges showing field label and value
    • Allow removing individual filters by removing them from filters object
    • Show "Clear all filters" button when filters are active

Combined Search and Filters

Search and field filters work together:

  • Both are stored in the filters object
  • 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

  1. 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 sort array in pagination parameters
    • Preserve all filters and search
    • Refetch data with updated sort
  2. 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
  3. Multi-Level Sort

    • When user clicks a second column header, add it to the sort array
    • Display multiple sort indicators if multiple columns are sorted
    • Allow removing individual sort fields

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

  1. 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
  2. Label Access Pattern

    • Access localized labels from label object: label[userLanguage]
    • Fall back to label.en if label for current language is missing
    • For select options: option.label[userLanguage] or option.label.en as fallback

Localized Components

Apply localization to:

  1. 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
  2. Table Columns

    • Column headers: attribute.label[userLanguage]
    • Cell values for select fields: option.label[userLanguage]
  3. Filter Controls

    • Filter labels: attribute.label[userLanguage]
    • Filter options: option.label[userLanguage]
  4. 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:

  1. Accept entityType as prop (e.g., "ChatWorkflow", "User", "Mandate")
  2. Fetch attributes: GET /api/attributes/{entityType}
  3. Fetch list data: GET /api/{entities}/?pagination=...
  4. Generate table columns from attributes
  5. Generate filter controls from attributes
  6. Handle pagination, search, filtering, and sorting
  7. 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 sort array
  • 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:

  1. Accept entityType and mode as props ("create" or "edit")
  2. Accept entityId as prop (for edit mode)
  3. Fetch attributes: GET /api/attributes/{entityType}
  4. Fetch entity data (for edit mode): GET /api/{entities}/{id}
  5. Generate form fields from attributes
  6. Handle form submission
  7. Handle validation

Form Field Generation:

  • Filter attributes: visible: true AND editable: 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:

  1. Accept attributes and items as props
  2. Generate columns from attributes (filter visible: true)
  3. Render table rows with formatted cell values
  4. Handle column header clicks for sorting
  5. Handle row clicks for navigation

Cell Value Formatting:

  • Use type property 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:

  1. Accept attributes and filters as props
  2. Generate filter controls from attributes (filter visible: true)
  3. Handle filter changes
  4. Display active filters as chips/badges
  5. 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

  1. Never Hardcode

    • Never hardcode field names, labels, types, or validation rules
    • Always fetch attribute definitions from backend
    • Use attribute metadata for all UI generation
  2. Entity-Agnostic

    • Same components work for all entity types
    • Pass entityType as parameter to determine endpoints
    • Use generic prop names (attributes, items, etc.)
  3. State Management

    • Keep pagination state separate from entity data
    • Preserve filters and sort when changing pages
    • Reset to page 1 when filters/search change
  4. Error Handling

    • Handle API errors gracefully
    • Display validation errors from backend
    • Show loading states during API calls
  5. Performance

    • Debounce search input (300-500ms)
    • Cache attribute definitions (don't refetch on every render)
    • Use pagination to limit data fetched
  6. 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.