Skip to content

Latest commit

 

History

History
1075 lines (893 loc) · 21.9 KB

File metadata and controls

1075 lines (893 loc) · 21.9 KB
title Action Protocol
description Buttons, triggers, navigation, and user interaction definitions

import { MousePointer, Zap, ExternalLink, Workflow, GitBranch, Play, Component, Layout } from 'lucide-react';

Action Protocol: Defining User Interactions

The Action Protocol specifies how user interactions (button clicks, menu selections, gestures) trigger business logic, navigation, or external integrations. Actions are the "verbs" of ObjectUI—they make things happen.

Philosophy: Declarative Interactions

Instead of writing onClick handlers in JavaScript, you declare what should happen in metadata:

# ❌ Imperative (React)
<Button onClick={() => {
  if (validate()) {
    api.post('/customers', data);
    router.push('/customers');
  }
}}>Save</Button>

# ✅ Declarative (ObjectUI)
action:
  type: standard_save
  label: Save
  onSuccess:
    - navigate: /customers

Benefits:

  • No code: Business analysts configure actions
  • Type-safe: Actions validated against schema
  • Testable: Actions are data, can be unit tested
  • Auditable: See what actions users can perform

Action Types

ObjectUI defines several categories of actions.

Standard Actions

Built-in CRUD operations that require no configuration:

actions:
  - type: standard_new
    label: New Customer
    object: customer
  
  - type: standard_edit
    label: Edit
    recordId: '{recordId}'
  
  - type: standard_delete
    label: Delete
    recordId: '{recordId}'
    confirm: true  # Show confirmation dialog
  
  - type: standard_save
    label: Save
    validate: true  # Run validation before saving
  
  - type: standard_cancel
    label: Cancel
    confirmIfDirty: true  # Warn if unsaved changes

Standard Actions Available:

  • standard_new - Create new record
  • standard_edit - Edit existing record
  • standard_delete - Delete record
  • standard_save - Save current form
  • standard_cancel - Cancel and return
  • standard_clone - Clone record
  • standard_refresh - Reload data
  • standard_export - Export to CSV/Excel
  • standard_print - Print view

Navigation Actions

Navigate to different pages or external URLs:

actions:
  - type: navigate
    label: View Dashboard
    target: /dashboards/sales_overview
  
  - type: navigate
    label: View Customer
    target: /customers/{customer_id}  # Dynamic parameter
  
  - type: navigate
    label: External Link
    target: https://example.com
    openInNewTab: true
  
  - type: navigate_back
    label: Go Back
    fallback: /customers  # If no history, go here

Flow Actions

Launch Screen Flows (multi-step wizards):

actions:
  - type: flow
    label: Approve Request
    flow: approval_wizard
    input:
      recordId: '{recordId}'
      approver: '{currentUserId}'
    onComplete:
      - action: refresh
      - action: toast
        message: Request approved successfully

API Actions

Call custom REST endpoints:

actions:
  - type: api
    label: Sync with ERP
    method: POST
    endpoint: /api/integrations/erp/sync
    params:
      customerId: '{recordId}'
    headers:
      X-Custom-Header: value
    onSuccess:
      - action: toast
        message: Sync completed successfully
        variant: success
    onError:
      - action: toast
        message: Sync failed: {error}
        variant: error

Workflow Actions

Trigger server-side workflows:

actions:
  - type: workflow
    label: Send Reminder
    workflow: send_customer_reminder
    input:
      customerId: '{recordId}'
      reminderType: payment_due
    async: true  # Don't wait for completion

Modal Actions

Open modals (dialogs) with embedded content:

actions:
  - type: modal
    label: Quick Edit
    title: Edit Customer
    content:
      type: form
      object: customer
      recordId: '{recordId}'
      fields: [name, email, phone]
    size: medium
    actions:
      - type: standard_save
        label: Save
        closeOnSuccess: true
      - type: standard_cancel
        label: Cancel

Bulk Actions

Operate on multiple selected records:

# In ListView
actions:
  - type: bulk_update
    label: Update Status
    availableWhen: rowsSelected
    fields:
      - name: status
        type: select
        options: [active, inactive, pending]
    onSuccess:
      - action: refresh
  
  - type: bulk_delete
    label: Delete Selected
    availableWhen: rowsSelected
    confirm:
      title: Delete {count} records?
      message: This action cannot be undone
      confirmLabel: Delete
      cancelLabel: Cancel

Custom Actions

Execute custom JavaScript (renderer-side):

actions:
  - type: custom
    label: Calculate Discount
    handler: calculateDiscount  # Function registered in renderer
    params:
      quantity: '{quantity}'
      unitPrice: '{unit_price}'

Action Configuration

Basic Action Properties

interface Action {
  // Identification
  type: ActionType;
  name?: string;                // Unique name
  label: string;                // Button text
  
  // Display
  icon?: string;                // Lucide icon
  variant?: 'primary' | 'secondary' | 'danger' | 'ghost';
  size?: 'small' | 'medium' | 'large';
  
  // Visibility & State
  visible?: VisibilityRule;     // When to show
  disabled?: DisabledRule;      // When to disable
  availableWhen?: Condition;    // Conditional availability
  
  // Confirmation
  confirm?: boolean | ConfirmDialog;
  
  // Execution
  onBefore?: Action[];          // Run before main action
  onSuccess?: Action[];         // Run on success
  onError?: Action[];           // Run on error
  onFinally?: Action[];         // Run always (cleanup)
}

Visibility Rules

Control when actions appear:

actions:
  # Show only if status is 'draft'
  - type: standard_edit
    label: Edit
    visible:
      field: status
      value: draft
  
  # Show only if user has permission
  - type: standard_delete
    label: Delete
    visible:
      permission: delete_customers
  
  # Show based on multiple conditions
  - type: workflow
    label: Approve
    visible:
      and:
        - { field: status, value: pending }
        - { permission: approve_requests }
        - { field: amount, operator: '<', value: 10000 }

Disabled Rules

Disable actions based on conditions:

actions:
  # Disable if form is invalid
  - type: standard_save
    label: Save
    disabled:
      formInvalid: true
  
  # Disable if record has children
  - type: standard_delete
    label: Delete
    disabled:
      field: has_open_orders
      value: true
    disabledTooltip: Cannot delete customer with open orders
  
  # Disable based on field value
  - type: workflow
    label: Ship Order
    disabled:
      or:
        - { field: status, operator: '!=', value: paid }
        - { field: inventory_available, value: false }

Confirmation Dialogs

Show confirmation before destructive actions.

Simple Confirmation

action:
  type: standard_delete
  label: Delete Customer
  confirm: true  # Default confirmation

Renders:

┌─────────────────────────────────┐
│ Confirm Delete                  │
├─────────────────────────────────┤
│ Are you sure you want to        │
│ delete this customer?           │
│                                 │
│       [Cancel]  [Delete]        │
└─────────────────────────────────┘

Custom Confirmation

action:
  type: bulk_delete
  label: Delete Selected
  confirm:
    title: Delete {count} customers?
    message: |
      This will permanently delete {count} customers
      and all related records. This action cannot
      be undone.
    icon: alert-triangle
    variant: danger
    confirmLabel: Yes, Delete
    cancelLabel: Cancel
    requireTyping: DELETE  # User must type "DELETE" to confirm

Parameter Binding

Actions can reference form data and context variables.

Variable Syntax

action:
  type: api
  endpoint: /api/customers/{customer_id}/send-email
  params:
    # Field values
    email: '{email}'
    name: '{name}'
    
    # System variables
    currentUser: '{$userId}'
    timestamp: '{$now}'
    
    # Computed values
    fullName: '{first_name} {last_name}'
    
    # Nested fields
    billingCity: '{billing_address.city}'

Available Variables

// Form field values
{fieldName}                     // e.g., {customer_name}

// System variables
{$userId}                       // Current user ID
{$userName}                     // Current user name
{$userEmail}                    // Current user email
{$now}                          // Current timestamp
{$recordId}                     // Current record ID
{$objectName}                   // Current object name

// Context variables (ListView)
{$selectedRows}                 // Selected row IDs
{$selectedCount}                // Count of selected rows
{$filteredCount}                // Count after filters

// Context variables (FormView)
{$isNew}                        // True if creating new record
{$isEditing}                    // True if editing existing
{$isDirty}                      // True if form has unsaved changes
{$isValid}                      // True if form passes validation

Action Chains

Execute multiple actions in sequence or parallel.

Sequential Actions

action:
  type: standard_save
  label: Save & Send Email
  onSuccess:
    - action: api
      type: api
      endpoint: /api/send-welcome-email
      params:
        customerId: '{recordId}'
    
    - action: navigate
      type: navigate
      target: /customers/{recordId}
    
    - action: toast
      type: toast
      message: Customer saved and email sent
      variant: success

Parallel Actions

action:
  type: standard_save
  label: Save
  onSuccess:
    parallel: true  # Execute in parallel
    actions:
      - type: api
        endpoint: /api/analytics/track
        params:
          event: customer_created
      
      - type: api
        endpoint: /api/crm/sync
        params:
          recordId: '{recordId}'
      
      - type: workflow
        workflow: send_notifications
        input:
          recordId: '{recordId}'

Conditional Chains

action:
  type: standard_save
  label: Save
  onSuccess:
    - when: { field: send_email, value: true }
      action:
        type: api
        endpoint: /api/send-email
    
    - when: { field: amount, operator: '>', value: 10000 }
      action:
        type: workflow
        workflow: high_value_alert

Action Placement

Actions can appear in different UI locations.

Form Actions

Buttons in form header or footer:

form:
  object: customer
  actions:
    placement: footer  # or 'header' | 'both'
    align: right       # or 'left' | 'center' | 'space-between'
    actions:
      - type: standard_save
        label: Save
        variant: primary
      - type: standard_cancel
        label: Cancel
        variant: secondary

Row Actions

Actions in list view rows:

listView:
  object: customer
  rowActions:
    - type: standard_edit
      label: Edit
      icon: edit
    
    - type: standard_delete
      label: Delete
      icon: trash
      confirm: true
    
    - type: separator  # Visual separator
    
    - type: api
      label: Send Email
      icon: mail
      endpoint: /api/customers/{_id}/send-email

Dropdown Menu Actions

Group actions in a dropdown:

actions:
  - type: menu
    label: More Actions
    icon: more-vertical
    items:
      - type: standard_clone
        label: Clone Customer
      - type: api
        label: Export to PDF
        endpoint: /api/customers/{_id}/export-pdf
      - type: separator
      - type: workflow
        label: Merge Duplicate
        workflow: merge_customers

Bulk Actions (List View)

Actions that operate on selected rows:

listView:
  object: customer
  bulkActions:
    - type: bulk_update
      label: Change Status
      icon: edit
      fields:
        - name: status
          type: select
          options: [active, inactive]
    
    - type: bulk_delete
      label: Delete
      icon: trash
      confirm: true

Quick Actions (Record Page)

Floating action buttons:

page:
  object: customer
  quickActions:
    - type: api
      label: Call Customer
      icon: phone
      endpoint: /api/telephony/call
      params:
        phone: '{phone}'
    
    - type: navigate
      label: View Map
      icon: map
      target: https://maps.google.com?q={address}
      openInNewTab: true

Navigation Patterns

Internal Navigation

# Navigate to object list
action:
  type: navigate
  target: /customers

# Navigate to specific record
action:
  type: navigate
  target: /customers/{customer_id}

# Navigate to related record
action:
  type: navigate
  target: /accounts/{account_id}
  params:
    recordId: '{account_id}'

# Navigate to dashboard
action:
  type: navigate
  target: /dashboards/sales_overview

External Navigation

# Open external URL
action:
  type: navigate
  label: View Website
  target: https://{website}
  openInNewTab: true

# Deep link to external app
action:
  type: navigate
  label: Open in Slack
  target: slack://channel?id={slack_channel_id}

# Email link
action:
  type: navigate
  label: Send Email
  target: mailto:{email}?subject=Hello&body=Message

Contextual Navigation

# Navigate with state
action:
  type: navigate
  target: /orders/new
  state:
    customerId: '{customer_id}'
    prefillData:
      shipping_address: '{address}'

# Navigate and replace history
action:
  type: navigate
  target: /customers
  replace: true  # Don't add to history stack

Integration Actions

Webhook Actions

Trigger external webhooks:

action:
  type: webhook
  label: Notify Slack
  url: https://hooks.slack.com/services/XXX/YYY/ZZZ
  method: POST
  headers:
    Content-Type: application/json
  body:
    text: "New customer: {name}"
    channel: "#sales"

OAuth Actions

Initiate OAuth flows:

action:
  type: oauth
  label: Connect Google Calendar
  provider: google
  scopes:
    - https://www.googleapis.com/auth/calendar
  onSuccess:
    - action: toast
      message: Google Calendar connected

File Upload Actions

Upload files to external storage:

action:
  type: file_upload
  label: Upload Attachment
  accept: .pdf,.docx,.xlsx
  maxSize: 10485760  # 10MB
  storage: s3
  bucket: customer-documents
  path: customers/{customer_id}/
  onSuccess:
    - action: update_field
      field: attachment_url
      value: '{uploadedUrl}'

State Management Actions

Update Field Values

action:
  type: update_field
  field: total_price
  value: '{quantity} * {unit_price}'

# Multiple fields
action:
  type: update_fields
  fields:
    status: completed
    completed_at: '{$now}'
    completed_by: '{$userId}'

Clear Fields

action:
  type: clear_fields
  fields: [shipping_address, shipping_method]

# Clear with condition
action:
  type: clear_fields
  when: { field: shipping_required, value: false }
  fields: [shipping_address]

Show/Hide Fields

action:
  type: show_fields
  fields: [billing_address, payment_method]

action:
  type: hide_fields
  fields: [internal_notes, api_key]

Toggle Sections

action:
  type: toggle_section
  section: advanced_settings

Feedback Actions

Toast Notifications

action:
  type: toast
  message: Customer saved successfully
  variant: success  # or 'error' | 'warning' | 'info'
  duration: 3000    # milliseconds
  position: top-right

# With action button
action:
  type: toast
  message: Draft saved
  variant: info
  action:
    label: View
    onClick:
      type: navigate
      target: /customers/{recordId}

Alert Dialogs

action:
  type: alert
  title: Error
  message: Failed to save customer
  variant: error
  buttons:
    - label: Retry
      action:
        type: standard_save
    - label: Cancel

Progress Indicators

action:
  type: api
  label: Generate Report
  endpoint: /api/reports/generate
  showProgress: true
  progressMessage: Generating report...
  onProgress:
    - action: update_progress
      percent: '{progress}'

Real-World Examples

Approve/Reject Workflow

actions:
  - type: workflow
    label: Approve
    icon: check-circle
    variant: primary
    workflow: approval_flow
    input:
      recordId: '{recordId}'
      decision: approved
      comments: '{approval_comments}'
    confirm:
      title: Approve this request?
      message: This will notify the requestor
    onSuccess:
      - action: toast
        message: Request approved
        variant: success
      - action: navigate
        target: /requests
  
  - type: workflow
    label: Reject
    icon: x-circle
    variant: danger
    workflow: approval_flow
    input:
      recordId: '{recordId}'
      decision: rejected
      comments: '{rejection_reason}'
    confirm:
      title: Reject this request?
      message: Please provide a reason in the comments
      requireField: rejection_reason
    onSuccess:
      - action: toast
        message: Request rejected
        variant: warning
      - action: navigate
        target: /requests

Send Email with Template

action:
  type: modal
  label: Send Email
  icon: mail
  title: Send Email to Customer
  content:
    type: form
    fields:
      - name: template
        type: select
        label: Email Template
        options:
          - { value: welcome, label: Welcome Email }
          - { value: invoice, label: Invoice Email }
          - { value: reminder, label: Payment Reminder }
      - name: subject
        type: text
        label: Subject
        value: '{template.subject}'
      - name: body
        type: richtext
        label: Message
        value: '{template.body}'
  actions:
    - type: api
      label: Send
      variant: primary
      endpoint: /api/emails/send
      method: POST
      params:
        to: '{email}'
        subject: '{subject}'
        body: '{body}'
        customerId: '{recordId}'
      onSuccess:
        - action: toast
          message: Email sent successfully
        - action: close_modal
    - type: standard_cancel
      label: Cancel

Multi-Step Data Migration

action:
  type: flow
  label: Migrate Data
  icon: database
  flow: data_migration_wizard
  steps:
    - name: select_source
      title: Select Source
      fields:
        - source_type
        - connection_string
    
    - name: field_mapping
      title: Map Fields
      component: field_mapper
      props:
        sourceFields: '{sourceFields}'
        targetObject: customer
    
    - name: validation
      title: Validate Data
      component: migration_validator
      props:
        mapping: '{fieldMapping}'
    
    - name: execute
      title: Execute Migration
      component: migration_progress
      onExecute:
        type: api
        endpoint: /api/migrations/execute
        method: POST
        params:
          mapping: '{fieldMapping}'
        async: true
  onComplete:
    - action: navigate
      target: /migrations/{migrationId}

Export with Options

action:
  type: modal
  label: Export
  icon: download
  title: Export Customers
  content:
    type: form
    fields:
      - name: format
        type: select
        label: Format
        options: [CSV, Excel, PDF]
        default: CSV
      - name: includeFields
        type: multiselect
        label: Fields to Include
        options: [name, email, phone, address, status]
      - name: filterBy
        type: select
        label: Filter
        options:
          - { value: all, label: All Customers }
          - { value: active, label: Active Only }
          - { value: selected, label: Selected Rows Only }
  actions:
    - type: api
      label: Export
      variant: primary
      endpoint: /api/customers/export
      method: POST
      params:
        format: '{format}'
        fields: '{includeFields}'
        filter: '{filterBy}'
        selectedIds: '{$selectedRows}'
      onSuccess:
        - action: download
          url: '{exportUrl}'
        - action: toast
          message: Export started. Download will begin shortly.
        - action: close_modal

Action Schema Reference

interface Action {
  // Core
  type: ActionType;
  name?: string;
  label: string;
  
  // Display
  icon?: string;
  variant?: 'primary' | 'secondary' | 'danger' | 'ghost' | 'link';
  size?: 'small' | 'medium' | 'large';
  fullWidth?: boolean;
  
  // State
  visible?: VisibilityRule;
  disabled?: DisabledRule;
  loading?: boolean;
  
  // Execution
  confirm?: boolean | ConfirmConfig;
  async?: boolean;
  debounce?: number;
  
  // Callbacks
  onBefore?: Action[];
  onSuccess?: Action[];
  onError?: Action[];
  onFinally?: Action[];
  
  // Type-specific props
  [key: string]: any;
}

type ActionType =
  | 'standard_new' | 'standard_edit' | 'standard_delete'
  | 'standard_save' | 'standard_cancel' | 'standard_clone'
  | 'navigate' | 'navigate_back'
  | 'flow' | 'api' | 'workflow'
  | 'modal' | 'toast' | 'alert'
  | 'bulk_update' | 'bulk_delete'
  | 'update_field' | 'clear_fields' | 'show_fields' | 'hide_fields'
  | 'webhook' | 'oauth' | 'file_upload'
  | 'custom';

What's Next?

} title="Layout DSL" href="/docs/protocols/objectui/layout-dsl" description="Learn how to structure pages and sections" /> } title="Widget Contract" href="/docs/protocols/objectui/widget-contract" description="Understand component props and events" />

Related Resources