MultiForm Custom API

Control your form like a boss — programmatic API to read/write data, manage steps, submit forms, and trigger actions from your JavaScript.

Multistep Form provides a public API for programmatic form control from external JavaScript code. This allows you to integrate the form with custom logic and dynamically modify form data and state.

API Access

The API is available through the global object window.FpsForm2_API[formId], where formId is the short component ID from form settings (data.params.comp_ID).

// Get form API by ID
const formAPI = window.FpsForm2_API['myFormId'];

Mounting and Timing Issues

Important: The form API is registered during component mount. If your script runs before the form is mounted, the API will be undefined.

Use this helper function to wait for the API to become available:

function waitForFormAPI(formId, callback, timeout = 5000) {
  const start = Date.now();
  const check = () => {
    if (window.FpsForm2_API && window.FpsForm2_API[formId]) {
      callback(window.FpsForm2_API[formId]);
    } else if (Date.now() - start < timeout) {
      setTimeout(check, 100);
    } else {
      console.error('Form API not available after ' + timeout + 'ms. Check form ID.');
    }
  };
  check();
}

// Usage
waitForFormAPI('myFormId', (formAPI) => {
  console.log('✅ API ready!');
  // Your code here
  formAPI.editModel('firstName', 'John');
});

Alternative: Use setTimeout with 500-1000ms delay if you know the form will mount quickly.


API Methods

Getting Data

getModel()

Returns the current form model (flat object with fields).

const model = formAPI.getModel();
console.log(model.firstName); // get field value

getExtendedModel()

Returns the extended form model (with nested objects for links/arrayLinks).

const extModel = formAPI.getExtendedModel();
console.log(extModel.company); // may be an object with structure

getState()

Returns the current form state (step, popup, custom fields).

const state = formAPI.getState();
console.log(state.step); // current step
console.log(state.popup); // opened popup

getOriginalModel()

Returns the original model (before user changes).

const original = formAPI.getOriginalModel();

Modifying Model

editModel(field, value)

Changes a single field in the model. Supports nested paths with dot notation.

// Simple field
formAPI.editModel('firstName', 'John');

// Nested field
formAPI.editModel('address.city', 'Moscow');

Important: Automatically updates both model and extendedModel.

setModel(newModelData)

Merges the provided object with the current model (merge, not replace).

formAPI.setModel({
  firstName: 'John',
  lastName: 'Doe',
  age: 30
});

replaceModel(newModel)

Completely replaces the model (replace, not merge).

formAPI.replaceModel({
  firstName: 'Jane',
  lastName: 'Smith'
});

Modifying State

editState(field, value)

Changes a single field in state. Supports nested paths.

// Change step
formAPI.editState('step', 'step2');

// Open popup
formAPI.editState('popup', 'confirmPopup');

// Close popup
formAPI.editState('popup', '');

// Custom field
formAPI.editState('myCustomFlag', true);

setState(newStateData)

Merges the provided object with the current state.

formAPI.setState({
  step: 'step3',
  customData: { foo: 'bar' }
});

replaceState(newState)

Completely replaces the state (use with caution, may break form logic).

formAPI.replaceState({
  step: 'newStep',
  popup: ''
});

Programmatic Submit

submit(options)

Programmatically triggers form submission.

// Simple submit
formAPI.submit();

// With options
formAPI.submit({
  submitKeepModel: true,  // don't reset model after submit
  targetStep: 'step3',    // go to step3 after success
  resetModel: false,      // don't reset model
  finish: (result) => {   // callback after completion
    console.log('Submit completed', result);
  }
});

Options:

  • finish — callback after completion

  • submitKeepModel — keep model after submit (default: true)

  • targetStep — step to navigate to after success

  • autoSubmit — autosubmit flag (default: false)

  • submitMapping — field mapping for submit

  • newData — additional data { model: {...}, state: {...} }

  • resetModel — reset model after submit


Calling Actions

callAction(actionIdOrName, callback)

Programmatically triggers a form action by ID or name.

// Call action by ID
formAPI.callAction('action_id_123', (success) => {
  if (success) {
    console.log('Action completed!');
  }
});

// Call action by name
formAPI.callAction('Submit Order', (success) => {
  console.log('Done:', success);
});

Supported action types:

  • state — updates state/model via stateMapping

  • endpoint — calls API endpoint with mapping

Limitations:

  • Does not support link, modal, or complex action types

  • For those, use direct API methods (editState, submit, etc.)

Note: For simpler cases, consider using the data-action mechanism in HTML elements. It's easier to implement and doesn't require JavaScript. See the data-action documentation for details.


Utilities

refreshOptions()

Refreshes dynamic options in form fields (for select/autocomplete with endpoints).

formAPI.refreshOptions();

Usage Examples

1. Validation and Data Modification

waitForFormAPI('registrationForm', (formAPI) => {
  // Get current data
  const model = formAPI.getModel();
  
  // Validation
  if (!model.email.includes('@')) {
    alert('Invalid email');
    formAPI.editModel('email', '');
  }
  
  // Auto-fill
  if (model.country === 'Russia') {
    formAPI.editModel('currency', 'RUB');
  }
});

2. Step Management

waitForFormAPI('wizardForm', (formAPI) => {
  
  // Next step
  function nextStep() {
    const state = formAPI.getState();
    const steps = ['step1', 'step2', 'step3', 'final'];
    const currentIndex = steps.indexOf(state.step);
    
    if (currentIndex < steps.length - 1) {
      formAPI.editState('step', steps[currentIndex + 1]);
    }
  }
  
  // Previous step
  function prevStep() {
    const state = formAPI.getState();
    const steps = ['step1', 'step2', 'step3', 'final'];
    const currentIndex = steps.indexOf(state.step);
    
    if (currentIndex > 0) {
      formAPI.editState('step', steps[currentIndex - 1]);
    }
  }
  
  // Attach to buttons
  document.getElementById('nextBtn').addEventListener('click', nextStep);
  document.getElementById('prevBtn').addEventListener('click', prevStep);
});

3. Syncing with External Data

waitForFormAPI('profileForm', (formAPI) => {
  
  // Load data from API
  async function loadUserData(userId) {
    const response = await fetch(`/api/users/${userId}`);
    const userData = await response.json();
    
    // Update form
    formAPI.setModel({
      firstName: userData.first_name,
      lastName: userData.last_name,
      email: userData.email
    });
  }
  
  loadUserData('123');
  
  // Subscribe to changes (polling)
  setInterval(() => {
    const model = formAPI.getModel();
    console.log('Current data:', model);
  }, 5000);
});

4. Custom Triggers and Calculations

waitForFormAPI('orderForm', (formAPI) => {
  
  // Recalculate total when quantity changes
  function recalculateTotal() {
    const model = formAPI.getModel();
    const quantity = parseInt(model.quantity) || 0;
    const price = parseFloat(model.price) || 0;
    const total = quantity * price;
    
    formAPI.editModel('total', total);
  }
  
  // Monitor changes (you can also use input events)
  document.getElementById('quantityInput').addEventListener('change', recalculateTotal);
  document.getElementById('priceInput').addEventListener('change', recalculateTotal);
});

5. Programmatically Opening Popups

waitForFormAPI('mainForm', (formAPI) => {
  
  // Open confirmation popup
  function showConfirmation() {
    formAPI.editState('popup', 'confirmationPopup');
  }
  
  // Close all popups
  function closePopups() {
    formAPI.editState('popup', '');
  }
  
  document.getElementById('confirmBtn').addEventListener('click', showConfirmation);
  document.getElementById('closeBtn').addEventListener('click', closePopups);
});

6. Calling Actions Programmatically

waitForFormAPI('checkoutForm', (formAPI) => {
  
  // Trigger an action
  document.getElementById('processBtn').addEventListener('click', () => {
    formAPI.callAction('Process Payment', (success) => {
      if (success) {
        alert('Payment processed!');
      } else {
        alert('Payment failed');
      }
    });
  });
});

Important Notes

  1. Form ID is required — API is only available if the FpsForm2 component has a short component ID in data.params.comp_ID.

  2. Timing is critical — Always use waitForFormAPI helper or setTimeout to ensure the form is mounted before accessing the API.

  3. Race conditions — Use setModel() for updating multiple fields at once, NOT multiple editModel() calls. Form inputs can override changes between sequential updates, causing data loss. Add 100-150ms setTimeout delay if inputs still override your changes.

  4. Model vs ExtendedModel — When modifying through the API, both objects are automatically synchronized.

  5. Autosubmit — If autosubmit on model change is enabled, programmatic changes via API will also trigger it.

  6. State persistence — If state saving to field (saveStateTo) is enabled, programmatic state changes will also be saved on submit.

  7. Refs — API uses refs to get current values, so it always returns fresh data.

  8. Cleanup — API is automatically removed from window.FpsForm2_API when component unmounts.

  9. Actions vs data-action — For simple button actions, consider using the data-action HTML attribute mechanism instead of programmatic callAction. It's simpler and requires no JavaScript code.


Complete Working Example

<!-- Simple control with two inputs and submit button -->
<div style="padding: 20px; border: 1px solid #ccc; border-radius: 8px; max-width: 400px;">
  
  <!-- Model field -->
  <div style="margin-bottom: 15px;">
    <label style="display: block; margin-bottom: 5px; font-weight: bold;">
      Model Field:
    </label>
    <input 
      type="text" 
      id="modelField" 
      placeholder="Enter value for model"
      style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;"
    />
  </div>
  
  <!-- State field -->
  <div style="margin-bottom: 15px;">
    <label style="display: block; margin-bottom: 5px; font-weight: bold;">
      State Field:
    </label>
    <input 
      type="text" 
      id="stateField" 
      placeholder="Enter value for state"
      style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;"
    />
  </div>
  
  <!-- Submit button -->
  <button 
    id="submitBtn"
    style="width: 100%; padding: 10px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px;"
  >
    Submit Form
  </button>
  
</div>

<script>
// ============= SETTINGS - CHANGE THESE =============
const FORM_ID = 'myFormId';              // Form component ID
const MODEL_FIELD_NAME = 'firstName';    // Model field name
const STATE_FIELD_NAME = 'customState';  // State field name
// ===================================================

// Helper to wait for API
function waitForFormAPI(formId, callback, timeout = 5000) {
  const start = Date.now();
  const check = () => {
    if (window.FpsForm2_API && window.FpsForm2_API[formId]) {
      callback(window.FpsForm2_API[formId]);
    } else if (Date.now() - start < timeout) {
      setTimeout(check, 100);
    } else {
      console.error('Form API not available after ' + timeout + 'ms');
    }
  };
  check();
}

// Wait for API to be ready
waitForFormAPI(FORM_ID, (formAPI) => {
  console.log('✅ Form API ready!', formAPI);
  
  // Model field - update model on input
  document.getElementById('modelField').addEventListener('input', (e) => {
    const value = e.target.value;
    formAPI.editModel(MODEL_FIELD_NAME, value);
    console.log(`Model updated: ${MODEL_FIELD_NAME} = ${value}`);
  });
  
  // State field - update state on input
  document.getElementById('stateField').addEventListener('input', (e) => {
    const value = e.target.value;
    formAPI.editState(STATE_FIELD_NAME, value);
    console.log(`State updated: ${STATE_FIELD_NAME} = ${value}`);
  });
  
  // Submit button
  document.getElementById('submitBtn').addEventListener('click', () => {
    console.log('Submitting form...');
    formAPI.submit({
      finish: (result) => {
        console.log('Submit completed!', result);
        alert('Form submitted successfully!');
      }
    });
  });
});
</script>

AI Prompt Template

Use this prompt template with any AI assistant (ChatGPT, Claude, etc.) to generate custom code for your form:

I'm working with Multistep Form component that has a public JavaScript API available at window.FpsForm2_API[formId].

Available API methods:
- getModel() - returns current form model (flat object)
- getExtendedModel() - returns extended model (with nested objects)
- getState() - returns current state (step, popup, custom fields)
- getOriginalModel() - returns original model before changes
- editModel(field, value) - change single field in model
- setModel(newModelData) - merge object with current model (PREFERRED for multiple fields)
- replaceModel(newModel) - completely replace model
- editState(field, value) - change single field in state
- setState(newStateData) - merge object with current state
- replaceState(newState) - completely replace state
- submit(options) - programmatically submit form
- callAction(actionIdOrName, callback) - trigger form action
- refreshOptions() - refresh dynamic field options

Important: The form API becomes available after component mount. Always use this helper:

function waitForFormAPI(formId, callback, timeout = 5000) {
  const start = Date.now();
  const check = () => {
    if (window.FpsForm2_API && window.FpsForm2_API[formId]) {
      callback(window.FpsForm2_API[formId]);
    } else if (Date.now() - start < timeout) {
      setTimeout(check, 100);
    } else {
      console.error('Form API not available after ' + timeout + 'ms');
    }
  };
  check();
}

CRITICAL: When updating multiple fields at once, ALWAYS use setModel() instead of multiple editModel() calls to avoid race conditions with form inputs:

// ❌ BAD - Can lose data due to race conditions:
formAPI.editModel('firstName', 'John');
formAPI.editModel('lastName', 'Doe');
formAPI.editModel('email', '[email protected]');

// ✅ GOOD - Atomic update, no race conditions:
formAPI.setModel({
  firstName: 'John',
  lastName: 'Doe',
  email: '[email protected]'
});

// Optional: Add 100-150ms delay if inputs still override your changes:
setTimeout(() => {
  formAPI.setModel({ /* your data */ });
}, 150);

**TASK**: [DESCRIBE YOUR TASK HERE]

Example tasks:
- Calculate total price when quantity or price fields change
- Validate email format and show error
- Auto-fill city based on selected country
- Navigate to next step when button is clicked
- Open popup when certain condition is met
- Load data from external API and populate form
- Clear specific fields when user clicks a button
- Call a form action programmatically
- Parse address from geocoding API and populate multiple address fields

Please generate JavaScript code that:
1. Uses waitForFormAPI helper to wait for the API
2. Gets the form API using the form ID
3. Uses setModel() for updating multiple fields (NOT multiple editModel calls)
4. Includes error handling
5. Has clear comments explaining what the code does
6. Can be easily integrated into HTML via <script> tag

Prompt Usage Examples:

Example 1: Calculate total

**TASK**: I have a form with fields 'quantity', 'price', and 'total'. When quantity or price changes, automatically calculate and update the total field.Form ID is 'orderForm'.

Example 2: Conditional field population

**TASK**: When user selects country='USA' in the dropdown, automatically set currency='USD' and timezone='America/New_York'.When country='UK', set currency='GBP' and timezone='Europe/London'.Form ID is 'registrationForm'.

Example 3: Custom validation

**TASK**: Before allowing user to proceed to 'step2', check that:
- email contains '@' symbol
- phone number is exactly 10 digits
- age is between 18 and 100
If validation fails, show alert and prevent step change.
Form ID is 'wizardForm'.

Example 4: External data integration

**TASK**: When form loads, fetch user data from '/api/users/123' and populate firstName, lastName, and email fields.Show loading indicator while fetching.Form ID is 'profileForm'.

Example 5: Action trigger

**TASK**: Add a button that triggers the form action named 'Send Email'when clicked, and shows an alert when the action completes.Form ID is 'contactForm'.

Pro tip: Just copy the prompt template, replace [DESCRIBE YOUR TASK HERE] with your specific requirement, and paste it into any AI chat. You'll get ready-to-use code! 🚀

Last updated

Was this helpful?