# Form Builder JavaScript API Reference

## Overview

The Form Builder JavaScript API enables developers to create dynamic, interactive forms with real-time calculations, conditional logic, and automated field updates. This API is powered by the FormScriptEngine component that executes JavaScript code safely within form contexts.

## Quick Start

### Basic Form Script Structure

```javascript
// Form initialization - runs once when form loads
onLoad: function() {
    console.log('Form is loading...');
    
    // Set default values
    setField('status', 'new');
    setField('date_created', new Date().toISOString().split('T')[0]);
}

// Field change handler - runs when any field value changes
onFieldChange: function(fieldName, value) {
    console.log('Field changed:', fieldName, '=', value);
    
    // Handle specific field changes
    if (fieldName === 'quantity' || fieldName === 'price') {
        updateTotal();
    }
}

// Helper functions (optional)
function updateTotal() {
    const quantity = getField('quantity') || 0;
    const price = getField('price') || 0;
    const total = quantity * price;
    
    setField('total', total.toFixed(2));
}
```

## Core Functions

### setField(fieldName, value)

Updates a form field value and triggers UI refresh.

**Parameters:**
- `fieldName` (string): The name/ID of the form field to update
- `value` (any): The new value to set for the field

**Returns:** `void`

**Examples:**
```javascript
// Set text field
setField('user_name', 'John Doe');

// Set number field
setField('age', 25);

// Set date field
setField('birth_date', '1999-01-15');

// Set boolean field
setField('is_active', true);

// Set calculated field
setField('total_amount', 149.99);

// Clear field
setField('notes', '');
```

**Notes:**
- Field names are case-sensitive and must match the form field's `name` attribute exactly
- Setting a field value will trigger the `onFieldChange` handler for that field
- Values are automatically converted to appropriate types based on field type

### getField(fieldName)

Retrieves the current value of a form field.

**Parameters:**
- `fieldName` (string): The name/ID of the form field to retrieve

**Returns:** The current field value (type depends on field type)

**Examples:**
```javascript
// Get text field value
const userName = getField('user_name'); // Returns string

// Get number field value with default
const quantity = getField('quantity') || 0; // Returns number or 0

// Get date field value
const birthDate = getField('birth_date'); // Returns string in YYYY-MM-DD format

// Get boolean field value
const isActive = getField('is_active'); // Returns boolean

// Check if field exists and has value
const email = getField('email');
if (email) {
    console.log('Email provided:', email);
}
```

**Notes:**
- Returns `undefined` if field doesn't exist
- Returns empty string `''` for empty text fields
- Returns `false` for unchecked boolean fields
- Use logical OR `||` to provide default values for empty fields

## Event Handlers

### onLoad

Executes once when the form initially loads. Use for initialization, setting default values, and performing initial calculations.

**Signature:**
```javascript
onLoad: function() {
    // Initialization code here
}
```

**Common Use Cases:**
```javascript
onLoad: function() {
    // Set default values
    setField('country', 'United States');
    setField('currency', 'USD');
    setField('created_date', new Date().toISOString().split('T')[0]);
    
    // Initialize calculated fields
    setField('subtotal', 0);
    setField('tax_amount', 0);
    setField('total', 0);
    
    // Set up initial state
    const userRole = getField('user_role');
    if (userRole === 'admin') {
        setField('permissions', 'full_access');
    }
    
    // Perform initial calculations
    calculateTotals();
}
```

### onFieldChange

Executes whenever a user changes any field value. Receives the field name and new value as parameters.

**Signature:**
```javascript
onFieldChange: function(fieldName, value) {
    // Handle field changes here
}
```

**Parameters:**
- `fieldName` (string): Name of the field that changed
- `value` (any): New value of the field

**Common Use Cases:**
```javascript
onFieldChange: function(fieldName, value) {
    console.log('Field updated:', fieldName, '=', value);
    
    // Handle specific fields
    switch(fieldName) {
        case 'quantity':
        case 'unit_price':
            calculateLineTotal();
            break;
            
        case 'country':
            updateTaxRate(value);
            updateShippingOptions(value);
            break;
            
        case 'subscription_type':
            updateFeatures(value);
            break;
            
        case 'birth_date':
            calculateAge(value);
            break;
    }
    
    // Handle multiple related fields
    if (['first_name', 'last_name'].includes(fieldName)) {
        updateFullName();
    }
    
    // Conditional logic
    if (fieldName === 'has_insurance' && value === true) {
        setField('insurance_provider', '');
        setField('policy_number', '');
    }
}
```

## Practical Examples

### Invoice Calculator

```javascript
onLoad: function() {
    // Initialize invoice
    setField('invoice_number', 'INV-' + Date.now());
    setField('invoice_date', new Date().toISOString().split('T')[0]);
    setField('tax_rate', 8.5); // 8.5% default tax
    
    // Initialize line items
    setField('quantity', 1);
    setField('unit_price', 0);
    setField('subtotal', 0);
    setField('tax_amount', 0);
    setField('total', 0);
}

onFieldChange: function(fieldName, value) {
    // Recalculate when quantity, price, or tax rate changes
    if (['quantity', 'unit_price', 'tax_rate'].includes(fieldName)) {
        const quantity = parseFloat(getField('quantity')) || 0;
        const unitPrice = parseFloat(getField('unit_price')) || 0;
        const taxRate = parseFloat(getField('tax_rate')) || 0;
        
        const subtotal = quantity * unitPrice;
        const taxAmount = (subtotal * taxRate) / 100;
        const total = subtotal + taxAmount;
        
        setField('subtotal', subtotal.toFixed(2));
        setField('tax_amount', taxAmount.toFixed(2));
        setField('total', total.toFixed(2));
    }
}
```

### User Registration with Validation

```javascript
onLoad: function() {
    setField('registration_date', new Date().toISOString().split('T')[0]);
    setField('account_status', 'pending');
}

onFieldChange: function(fieldName, value) {
    // Email validation
    if (fieldName === 'email') {
        const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        const isValid = emailPattern.test(value);
        
        setField('email_valid', isValid);
        setField('email_status', isValid ? 'Valid' : 'Invalid email format');
    }
    
    // Password strength
    if (fieldName === 'password') {
        let strength = 'Weak';
        
        if (value.length >= 8) {
            if (/[A-Z]/.test(value) && /[a-z]/.test(value) && /\d/.test(value)) {
                strength = 'Strong';
            } else {
                strength = 'Medium';
            }
        }
        
        setField('password_strength', strength);
    }
    
    // Full name combination
    if (['first_name', 'last_name'].includes(fieldName)) {
        const firstName = getField('first_name') || '';
        const lastName = getField('last_name') || '';
        const fullName = (firstName + ' ' + lastName).trim();
        
        setField('full_name', fullName);
    }
}
```

### E-commerce Order Form

```javascript
onLoad: function() {
    setField('order_date', new Date().toISOString().split('T')[0]);
    setField('shipping_method', 'standard');
    setField('gift_wrap', false);
    
    // Initialize pricing
    updateShipping();
}

onFieldChange: function(fieldName, value) {
    // Product selection
    if (fieldName === 'product') {
        const prices = {
            'laptop': 999.99,
            'mouse': 29.99,
            'keyboard': 79.99,
            'monitor': 299.99
        };
        
        const price = prices[value] || 0;
        setField('unit_price', price);
    }
    
    // Quantity and pricing updates
    if (['quantity', 'unit_price'].includes(fieldName)) {
        const quantity = parseFloat(getField('quantity')) || 0;
        const unitPrice = parseFloat(getField('unit_price')) || 0;
        const itemTotal = quantity * unitPrice;
        
        setField('item_total', itemTotal.toFixed(2));
        updateOrderTotal();
    }
    
    // Shipping calculation
    if (['shipping_method', 'country'].includes(fieldName)) {
        updateShipping();
    }
    
    // Gift wrap fee
    if (fieldName === 'gift_wrap') {
        const giftWrapFee = value ? 9.99 : 0;
        setField('gift_wrap_fee', giftWrapFee.toFixed(2));
        updateOrderTotal();
    }
}

function updateShipping() {
    const method = getField('shipping_method');
    const country = getField('country');
    
    let shippingCost = 0;
    
    if (country === 'US') {
        switch(method) {
            case 'standard': shippingCost = 5.99; break;
            case 'express': shippingCost = 14.99; break;
            case 'overnight': shippingCost = 24.99; break;
        }
    } else {
        switch(method) {
            case 'standard': shippingCost = 19.99; break;
            case 'express': shippingCost = 39.99; break;
        }
    }
    
    setField('shipping_cost', shippingCost.toFixed(2));
    updateOrderTotal();
}

function updateOrderTotal() {
    const itemTotal = parseFloat(getField('item_total')) || 0;
    const shippingCost = parseFloat(getField('shipping_cost')) || 0;
    const giftWrapFee = parseFloat(getField('gift_wrap_fee')) || 0;
    
    const orderTotal = itemTotal + shippingCost + giftWrapFee;
    setField('order_total', orderTotal.toFixed(2));
}
```

### Loan Calculator

```javascript
onLoad: function() {
    // Set default loan parameters
    setField('loan_amount', 100000);
    setField('interest_rate', 4.5);
    setField('loan_term_years', 30);
    
    // Calculate initial payment
    calculateLoanPayment();
}

onFieldChange: function(fieldName, value) {
    if (['loan_amount', 'interest_rate', 'loan_term_years'].includes(fieldName)) {
        calculateLoanPayment();
    }
}

function calculateLoanPayment() {
    const principal = parseFloat(getField('loan_amount')) || 0;
    const annualRate = parseFloat(getField('interest_rate')) || 0;
    const years = parseFloat(getField('loan_term_years')) || 0;
    
    if (principal > 0 && annualRate > 0 && years > 0) {
        const monthlyRate = (annualRate / 100) / 12;
        const numberOfPayments = years * 12;
        
        const monthlyPayment = principal * 
            (monthlyRate * Math.pow(1 + monthlyRate, numberOfPayments)) /
            (Math.pow(1 + monthlyRate, numberOfPayments) - 1);
        
        const totalPayments = monthlyPayment * numberOfPayments;
        const totalInterest = totalPayments - principal;
        
        setField('monthly_payment', monthlyPayment.toFixed(2));
        setField('total_payments', totalPayments.toFixed(2));
        setField('total_interest', totalInterest.toFixed(2));
    } else {
        setField('monthly_payment', '0.00');
        setField('total_payments', '0.00');
        setField('total_interest', '0.00');
    }
}
```

## Available Global Objects

### Math Object
Full JavaScript Math object with all methods:

```javascript
// Common math operations
const rounded = Math.round(getField('price') * 1.1);
const randomId = Math.floor(Math.random() * 1000000);
const maxValue = Math.max(getField('value1'), getField('value2'));

// Advanced math
const power = Math.pow(getField('base'), getField('exponent'));
const squareRoot = Math.sqrt(getField('area'));
```

### Date Object
Full JavaScript Date object for date/time operations:

```javascript
// Current date/time
const now = new Date();
const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD format

// Date calculations
const birthDate = new Date(getField('birth_date'));
const age = Math.floor((Date.now() - birthDate.getTime()) / (365.25 * 24 * 60 * 60 * 1000));

// Date formatting
const formatted = new Date().toLocaleDateString('en-US', {
    year: 'numeric',
    month: 'long',
    day: 'numeric'
});
```

### String Methods
Standard JavaScript string methods:

```javascript
// String manipulation
const name = getField('user_name').toUpperCase();
const initials = getField('first_name').charAt(0) + getField('last_name').charAt(0);
const slug = getField('title').toLowerCase().replace(/\s+/g, '-');
```

### Number Parsing
Standard parsing functions:

```javascript
// Parse numbers
const quantity = parseInt(getField('quantity_str')) || 0;
const price = parseFloat(getField('price_str')) || 0.0;

// Validation
const isValidNumber = !isNaN(parseFloat(getField('amount')));
```

### Console Logging
Console object for debugging:

```javascript
// Debug logging
console.log('Processing field:', fieldName, 'with value:', value);
console.warn('Invalid input detected:', value);
console.error('Calculation failed:', error);

// Grouped logging
console.group('Order Calculation');
console.log('Subtotal:', subtotal);
console.log('Tax:', tax);
console.log('Total:', total);
console.groupEnd();
```

## Best Practices

### Error Handling
```javascript
onFieldChange: function(fieldName, value) {
    try {
        if (fieldName === 'price') {
            const price = parseFloat(value);
            
            if (isNaN(price)) {
                console.warn('Invalid price value:', value);
                setField('price_error', 'Please enter a valid number');
                return;
            }
            
            if (price < 0) {
                console.warn('Negative price not allowed:', price);
                setField('price_error', 'Price cannot be negative');
                return;
            }
            
            // Clear error and proceed
            setField('price_error', '');
            calculateTotal(price);
        }
    } catch (error) {
        console.error('Error processing field change:', error);
    }
}
```

### Performance Optimization
```javascript
// Cache calculations
let cachedTaxRate = null;

function getTaxRate(country) {
    if (cachedTaxRate === null) {
        const rates = {
            'US': 8.5,
            'CA': 12.0,
            'UK': 20.0
        };
        cachedTaxRate = rates[country] || 0;
    }
    return cachedTaxRate;
}

// Minimize calculations
onFieldChange: function(fieldName, value) {
    // Only calculate when relevant fields change
    if (['quantity', 'price', 'tax_rate'].includes(fieldName)) {
        // Batch related calculations
        updateAllTotals();
    }
    
    // Avoid recalculating if value hasn't actually changed
    const currentTotal = getField('total');
    const newTotal = calculateNewTotal();
    
    if (currentTotal !== newTotal) {
        setField('total', newTotal);
    }
}
```

### Data Validation
```javascript
// Validation helper functions
function isValidEmail(email) {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

function isValidPhone(phone) {
    return /^\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$/.test(phone);
}

function validateCreditCard(number) {
    // Luhn algorithm
    const digits = number.replace(/\D/g, '');
    let sum = 0;
    let isEven = false;
    
    for (let i = digits.length - 1; i >= 0; i--) {
        let digit = parseInt(digits[i]);
        
        if (isEven) {
            digit *= 2;
            if (digit > 9) digit -= 9;
        }
        
        sum += digit;
        isEven = !isEven;
    }
    
    return sum % 10 === 0;
}

// Use in field change handler
onFieldChange: function(fieldName, value) {
    if (fieldName === 'email') {
        const valid = isValidEmail(value);
        setField('email_valid', valid);
        setField('email_message', valid ? 'Valid email' : 'Invalid email format');
    }
}
```

## Common Patterns

### Conditional Field Display
```javascript
// Simulate showing/hiding fields by setting values
onFieldChange: function(fieldName, value) {
    if (fieldName === 'account_type') {
        if (value === 'business') {
            setField('company_name', '');
            setField('tax_id', '');
            setField('show_business_fields', 'true');
        } else {
            setField('show_business_fields', 'false');
        }
    }
}
```

### Multi-step Calculations
```javascript
// Break complex calculations into steps
function calculateShippingCost() {
    const weight = parseFloat(getField('package_weight')) || 0;
    const distance = parseFloat(getField('shipping_distance')) || 0;
    const method = getField('shipping_method');
    
    // Step 1: Base cost by weight
    let baseCost = 0;
    if (weight <= 1) baseCost = 5.99;
    else if (weight <= 5) baseCost = 12.99;
    else baseCost = 5.99 + (weight - 1) * 2.50;
    
    // Step 2: Distance multiplier
    let distanceMultiplier = 1;
    if (distance > 500) distanceMultiplier = 1.5;
    else if (distance > 100) distanceMultiplier = 1.2;
    
    // Step 3: Method adjustment
    let methodMultiplier = 1;
    switch(method) {
        case 'express': methodMultiplier = 2; break;
        case 'overnight': methodMultiplier = 3; break;
    }
    
    // Final calculation
    const finalCost = baseCost * distanceMultiplier * methodMultiplier;
    
    setField('shipping_cost', finalCost.toFixed(2));
    setField('shipping_breakdown', `Base: $${baseCost} × Distance: ${distanceMultiplier} × Method: ${methodMultiplier}`);
}
```

### Form State Management
```javascript
onLoad: function() {
    // Initialize form state
    setField('form_state', 'initialized');
    setField('validation_errors', '');
    setField('completion_percentage', 0);
}

onFieldChange: function(fieldName, value) {
    // Update completion percentage
    updateCompletionPercentage();
    
    // Track form state
    setField('form_state', 'editing');
    setField('last_modified', new Date().toISOString());
}

function updateCompletionPercentage() {
    const requiredFields = ['first_name', 'last_name', 'email', 'phone'];
    let completedFields = 0;
    
    requiredFields.forEach(field => {
        if (getField(field) && getField(field).trim() !== '') {
            completedFields++;
        }
    });
    
    const percentage = Math.round((completedFields / requiredFields.length) * 100);
    setField('completion_percentage', percentage);
}
```

## Debugging and Troubleshooting

### Common Issues

**1. Field names don't match**
```javascript
// ❌ Wrong - case sensitive
setField('User_Name', 'John'); // Field is actually 'user_name'

// ✅ Correct
setField('user_name', 'John');
```

**2. Missing error handling**
```javascript
// ❌ Without error handling
const price = parseFloat(getField('price'));
const total = price * quantity; // Will fail if price is invalid

// ✅ With error handling
const price = parseFloat(getField('price')) || 0;
const quantity = parseFloat(getField('quantity')) || 0;
const total = price * quantity;
```

**3. Infinite loops in onFieldChange**
```javascript
// ❌ Can cause infinite loop
onFieldChange: function(fieldName, value) {
    if (fieldName === 'total') {
        setField('total', value * 1.1); // This will trigger onFieldChange again!
    }
}

// ✅ Avoid triggering changes for calculated fields
onFieldChange: function(fieldName, value) {
    if (fieldName === 'quantity' || fieldName === 'price') {
        const total = getField('quantity') * getField('price');
        setField('total', total); // Only update when input fields change
    }
}
```

### Debug Techniques

```javascript
// Enable detailed logging
onFieldChange: function(fieldName, value) {
    console.group(`Field Change: ${fieldName}`);
    console.log('New Value:', value);
    console.log('Current Form Data:', {
        quantity: getField('quantity'),
        price: getField('price'),
        total: getField('total')
    });
    
    // Your calculation logic here
    
    console.groupEnd();
}

// Add validation checkpoints
function calculateTotal() {
    const quantity = getField('quantity');
    const price = getField('price');
    
    console.log('Calculation inputs:', { quantity, price });
    
    if (isNaN(quantity) || isNaN(price)) {
        console.error('Invalid inputs for calculation');
        return;
    }
    
    const total = quantity * price;
    console.log('Calculated total:', total);
    
    setField('total', total.toFixed(2));
}
```

---

*This API reference covers all available JavaScript functionality in the Form Builder. For implementation examples and integration details, see the [Technical Guide](TECHNICAL_GUIDE.md) and [User Guide](USER_GUIDE.md).*

Last updated: December 2024 