Form Configuration

Complete guide to configuring forms with all features - context, services, validations, computed fields, and lifecycle hooks working together.

⚙️

What is Form Configuration?

Form configuration is the complete JSON structure that defines your form's behavior, data, validations, and dynamic features. It brings together all the form builder's capabilities into a single, cohesive definition.

Complete Form Configuration Structure

json
{
  "id": "completeExampleForm",
  "type": "form",
  "metadata": {
    "formName": "Complete Feature Example",
    "version": "1.0",
    "description": "Demonstrates all form features"
  },
  "initialPage": "personal",
  "context": {
    "user": {
      "firstName": "",
      "lastName": "",
      "email": "",
      "preferences": {
        "newsletter": true,
        "notifications": false
      }
    },
    "order": {
      "items": [],
      "subtotal": 0,
      "tax": 0,
      "total": 0,
      "discountCode": ""
    },
    "location": {
      "zipCode": "",
      "city": "",
      "state": "",
      "taxRate": 0
    }
  },
  "services": {
    "loadUserProfile": {
      "type": "rest",
      "url": "/api/users/{{context:user.email}}",
      "method": "GET"
    },
    "lookupTaxRate": {
      "type": "rest",
      "url": "/api/tax-rate/{{context:location.zipCode}}",
      "method": "GET"
    },
    "submitOrder": {
      "type": "rest",
      "url": "/api/orders",
      "method": "POST",
      "format": "json",
      "body": {
        "user": "{{context:user}}",
        "order": "{{context:order}}"
      }
    },
    "calculateDiscount": {
      "type": "function",
      "function": "applyDiscountCode"
    }
  },
  "validations": {
    "email": {
      "rule": "pattern",
      "value": "^[^\s@]+@[^\s@]+\.[^\s@]+$",
      "message": "Please enter a valid email address"
    }
  },
  "computedFields": {
    "fullName": {
      "targetPath": "user.fullName",
      "expression": "context.user.firstName + ' ' + context.user.lastName",
      "dependencies": ["user.firstName", "user.lastName"]
    },
    "orderTotal": {
      "targetPath": "order.total",
      "expression": "context.order.subtotal + context.order.tax",
      "dependencies": ["order.subtotal", "order.tax"]
    }
  },
  "lifecycle": {
    "onFormLoad": [
      {
        "type": "callApi",
        "service": "loadUserProfile",
        "then": [
          {
            "type": "assignContext",
            "assign": {
              "user": {"{{event}}"}
            }
          }
        ]
      }
    ],
    "onContextChange": [
      {
        "watch": ["location.zipCode"],
        "actions": [
          {
            "type": "callApi",
            "service": "lookupTaxRate",
            "then": [
              {
                "type": "assignContext",
                "assign": {
                  "location.taxRate": {"{{event.rate}}"}
                }
              }
            ]
          }
        ]
      },
      {
        "watch": ["order.discountCode"],
        "actions": [
          {
            "type": "callApi",
            "service": "calculateDiscount",
            "then": [
              {
                "type": "assignContext",
                "assign": {
                  "order.discount": {"{{event.discount}}"}
                }
              }
            ]
          }
        ]
      }
    ],
    "beforeSubmit": [
      {
        "type": "validate",
        "targets": ["user.email", "user.firstName"]
      }
    ]
  },
  "blocks": [...],
  "pages": {...}
}

Configuration Sections Breakdown

📋 Basic Configuration

  • id: Unique form identifier
  • type: Always "form"
  • metadata: Form name, version, description
  • initialPage: First page to show

🎯 Data & State

  • context: Form data and state
  • computedFields: Auto-calculated values
  • services: API calls and functions
  • validations: Field validation rules

⚡ Dynamic Behavior

  • lifecycle: Event-driven actions
  • onFormLoad: Initial data loading
  • onContextChange: Reactive updates
  • beforeSubmit: Final validation

🎨 User Interface

  • blocks: Individual form components
  • pages: Form page definitions
  • className: CSS styling
  • displayConditions: Conditional visibility

Feature Integration Examples

1. Auto-Populating Form with API Data

json
{
  "context": {
    "user": {
      "firstName": "",
      "lastName": "",
      "email": ""
    }
  },
  "services": {
    "loadProfile": {
      "type": "rest",
      "url": "/api/user/{{context:user.email}}",
      "method": "GET"
    }
  },
  "lifecycle": {
    "onFormLoad": [
      {
        "type": "callApi",
        "service": "loadProfile",
        "then": [
          {
            "type": "assignContext",
            "assign": {
              "user.firstName": {"{{event.firstName}}"},
              "user.lastName": {"{{event.lastName}}"}
            }
          }
        ]
      }
    ]
  }
}

2. Dependent Dropdowns with Validation

json
{
  "context": {
    "location": {
      "country": "",
      "state": "",
      "city": ""
    }
  },
  "services": {
    "loadStates": {
      "type": "rest",
      "url": "/api/states/{{context:location.country}}",
      "method": "GET"
    },
    "loadCities": {
      "type": "rest",
      "url": "/api/cities/{{context:location.state}}",
      "method": "GET"
    }
  },
  "lifecycle": {
    "onContextChange": [
      {
        "watch": ["location.country"],
        "actions": [
          {
            "type": "callApi",
            "service": "loadStates",
            "then": [
              {
                "type": "assignContext",
                "assign": {
                  "location.states": {"{{event}}"}
                }
              }
            ]
          }
        ]
      },
      {
        "watch": ["location.state"],
        "actions": [
          {
            "type": "callApi",
            "service": "loadCities",
            "then": [
              {
                "type": "assignContext",
                "assign": {
                  "location.cities": {"{{event}}"}
                }
              }
            ]
          }
        ]
      }
    ]
  },
  "validations": {
    "state": {
      "rule": "exact",
      "value": {"{{context:location.states}}"},
      "message": "Please select a valid state"
    }
  }
}

3. Shopping Cart with Real-time Calculations

json
{
  "context": {
    "cart": {
      "items": [],
      "subtotal": 0,
      "taxRate": 0.08,
      "tax": 0,
      "discount": 0,
      "total": 0
    }
  },
  "services": {
    "calculateTax": {
      "type": "function",
      "function": "calculateOrderTax"
    }
  },
  "computedFields": {
    "subtotal": {
      "targetPath": "cart.subtotal",
      "expression": "context.cart.items.reduce((sum, item) => sum + (item.price * item.quantity), 0)",
      "dependencies": ["cart.items"]
    },
    "tax": {
      "targetPath": "cart.tax",
      "expression": "Math.round(context.cart.subtotal * context.cart.taxRate * 100) / 100",
      "dependencies": ["cart.subtotal", "cart.taxRate"]
    },
    "total": {
      "targetPath": "cart.total",
      "expression": "context.cart.subtotal + context.cart.tax - context.cart.discount",
      "dependencies": ["cart.subtotal", "cart.tax", "cart.discount"]
    }
  },
  "lifecycle": {
    "onContextChange": [
      {
        "watch": ["cart.items", "cart.taxRate"],
        "actions": [
          {
            "type": "callApi",
            "service": "calculateTax",
            "then": [
              {
                "type": "assignContext",
                "assign": {
                  "cart.calculatedTax": {"{{event.tax}}"}
                }
              }
            ]
          }
        ]
      }
    ]
  }
}

Advanced Patterns

Multi-Step Workflow with Progress Tracking

json
{
  "id": "multiStepWorkflow",
  "initialPage": "step1",
  "context": {
    "progress": {
      "currentStep": 1,
      "totalSteps": 4,
      "completedSteps": []
    },
    "formData": {
      "step1": {},
      "step2": {},
      "step3": {},
      "step4": {}
    }
  },
  "services": {
    "saveProgress": {
      "type": "rest",
      "url": "/api/save-progress",
      "method": "POST",
      "body": {
        "step": {"{{context:progress.currentStep}}"},
        "data": {"{{context:formData}}"}
      }
    }
  },
  "lifecycle": {
    "onPageChange": [
      {
        "type": "assignContext",
        "assign": {
          "progress.currentStep": {"{{event:newPage}}"}
        }
      },
      {
        "type": "callApi",
        "service": "saveProgress"
      }
    ]
  },
  "computedFields": {
    "progressPercentage": {
      "targetPath": "progress.percentage",
      "expression": "Math.round((context.progress.completedSteps.length / context.progress.totalSteps) * 100)",
      "dependencies": ["progress.completedSteps", "progress.totalSteps"]
    }
  }
}

Conditional Form Sections

json
{
  "context": {
    "user": {
      "accountType": "personal",
      "businessInfo": {
        "companyName": "",
        "taxId": ""
      }
    }
  },
  "blocks": [
    {
      "id": "account-type-selector",
      "blockType": "SelectBlock",
      "metadata": {
        "label": "Account Type",
        "contextPath": "user.accountType",
        "options": [
          { "label": "Personal", "value": "personal" },
          { "label": "Business", "value": "business" }
        ]
      }
    },
    {
      "id": "business-section",
      "blockType": "CardBlock",
      "displayConditions": [
        {
          "matchType": "exact",
          "field": "user.accountType",
          "value": "business"
        }
      ],
      "blocks": [
        {
          "blockType": "InputBlock",
          "metadata": {
            "label": "Company Name",
            "contextPath": "user.businessInfo.companyName",
            "required": true
          }
        }
      ]
    }
  ]
}

Best Practices

✅ Organization Tips

  • • Group related context properties together
  • • Use descriptive service names
  • • Keep lifecycle hooks focused on single responsibilities
  • • Use computed fields for simple calculations

⚠️ Performance Considerations

  • • Always use watch arrays in lifecycle hooks
  • • Avoid deep nesting in context paths
  • • Cache expensive API calls when possible
  • • Use computed fields instead of repeated calculations

🔧 Debugging Strategies

  • • Test services independently before integration
  • • Use console logging in custom functions
  • • Validate context paths exist before using them
  • • Handle API errors gracefully with fallbacks

Complete Working Example

Here's a complete form configuration that demonstrates all features working together:

🎯 View Complete Example

Check out the comprehensive example that shows all features in action:

View Complete Form Example