Skip to content

Form Hooks API

The Form component exposes an API through window.$useFormCore that allows external JavaScript to interact with form instances.


// Hooks are available after form.js is loaded
const { onChange, setSpecInPath } = window.$useFormCore;

HookSignatureDescription
onChange(formId: string, callback: Function) => voidSubscribe to form value changes
setSpecInPath(formId: string, path: string, newSpec: Field) => voidDynamically update form structure

Subscribe to value changes for a specific form instance.

onChange(
formId: string,
callback: (event: CustomEvent<[string, any, string]>) => void
): void
ParameterTypeDescription
formIdstringThe unique ID of the form to subscribe to
callbackFunctionCalled when any field value changes

The callback receives a CustomEvent with a detail array containing:

IndexTypeDescription
detail[0]stringPath of the changed field (e.g., "user.email")
detail[1]anyNew value of the field
detail[2]stringForm ID
const { onChange } = window.$useFormCore;
onChange('my-form', (event) => {
const [path, value, formId] = event.detail;
console.log(`Field "${path}" changed to:`, value);
console.log('Form ID:', formId);
// React to specific field changes
if (path === 'request.integration') {
loadIntegrationDetails(value);
}
});
<script setup lang="ts">
import { onMounted, onUnmounted } from 'vue';
const formId = 'dynamic-form';
onMounted(() => {
const { onChange } = window.$useFormCore;
onChange(formId, (event) => {
const [path, value] = event.detail;
// Auto-fill related fields
if (path === 'address.zipCode' && value.length === 5) {
fetchCityByZip(value);
}
});
});
</script>
<template>
<form-component :id="formId" :spec="spec" />
</template>

Dynamically replace a field’s specification at runtime. This allows you to:

  • Change field types based on user selections
  • Add/remove validation rules
  • Update select options dynamically
  • Modify labels or placeholders
setSpecInPath(
formId: string,
path: string,
newSpec: Field
): void
ParameterTypeDescription
formIdstringThe unique ID of the form
pathstringDot-notation path to the field (e.g., "request.body")
newSpecFieldNew field specification to replace the existing one
const { setSpecInPath } = window.$useFormCore;
// Original spec had: { type: 'text', name: 'body', label: 'Body' }
// Replace with a select field:
setSpecInPath('my-form', 'request.body', {
type: 'select',
name: 'body',
label: 'Request Body Template',
options: [
{ label: 'JSON', value: 'json' },
{ label: 'XML', value: 'xml' },
{ label: 'Form Data', value: 'form' },
],
});

Example: Dynamic Options Based on Selection

Section titled “Example: Dynamic Options Based on Selection”
const { onChange, setSpecInPath } = window.$useFormCore;
// Listen for integration selection
onChange('action-form', (event) => {
const [path, value] = event.detail;
if (path === 'request.integration') {
// Load actions for selected integration
const actions = await fetchIntegrationActions(value);
// Update the action select options
setSpecInPath('action-form', 'request.action', {
type: 'select',
name: 'action',
label: 'Action',
required: true,
options: actions.map(a => ({
label: a.name,
value: a.id,
description: a.description,
})),
});
}
});

The path follows dot notation matching the name properties in your spec:

// Given this spec:
const spec = {
type: 'collection',
name: 'request',
spec: [
{
type: 'text',
name: 'url',
label: 'URL',
},
{
type: 'collection',
name: 'headers',
spec: [
{ type: 'text', name: 'contentType', label: 'Content-Type' },
],
},
],
};
// Paths would be:
// - "request.url" -> the URL field
// - "request.headers" -> the headers collection
// - "request.headers.contentType" -> the Content-Type field

Here’s a complete example showing how to build a dynamic form that changes based on user selections:

<!DOCTYPE html>
<html>
<head>
<script src="./dist/form.js"></script>
</head>
<body>
<form-component id="integration-form"></form-component>
<script>
// Initial spec with integration selector
const initialSpec = {
type: 'collection',
name: 'config',
spec: [
{
type: 'select',
name: 'provider',
label: 'Provider',
required: true,
options: [
{ label: 'Shopify', value: 'shopify' },
{ label: 'WooCommerce', value: 'woocommerce' },
{ label: 'Magento', value: 'magento' },
],
},
{
type: 'text',
name: 'credentials',
label: 'API Key',
placeholder: 'Select a provider first',
readonly: true,
},
],
};
// Set spec on form
const form = document.getElementById('integration-form');
form.spec = JSON.stringify(initialSpec);
// Get hooks
const { onChange, setSpecInPath } = window.$useFormCore;
// Provider-specific credential specs
const providerSpecs = {
shopify: {
type: 'collection',
name: 'credentials',
spec: [
{ type: 'text', name: 'shopName', label: 'Shop Name', required: true },
{ type: 'password', name: 'apiKey', label: 'API Key', required: true },
{ type: 'password', name: 'apiSecret', label: 'API Secret', required: true },
],
},
woocommerce: {
type: 'collection',
name: 'credentials',
spec: [
{ type: 'url', name: 'storeUrl', label: 'Store URL', required: true },
{ type: 'password', name: 'consumerKey', label: 'Consumer Key', required: true },
{ type: 'password', name: 'consumerSecret', label: 'Consumer Secret', required: true },
],
},
magento: {
type: 'collection',
name: 'credentials',
spec: [
{ type: 'url', name: 'baseUrl', label: 'Magento URL', required: true },
{ type: 'password', name: 'accessToken', label: 'Access Token', required: true },
],
},
};
// Listen for provider changes
onChange('integration-form', (event) => {
const [path, value] = event.detail;
if (path === 'config.provider' && providerSpecs[value]) {
// Replace credentials field with provider-specific spec
setSpecInPath('integration-form', 'config.credentials', providerSpecs[value]);
}
});
// Handle form submission
form.addEventListener('submit', (e) => {
console.log('Integration configured:', e.detail);
});
</script>
</body>
</html>

If you’re using TypeScript, you can add type declarations for the hooks:

types/form-hooks.d.ts
import type { Field } from '@ion-components/ui/types';
declare global {
interface Window {
$useFormCore: {
onChange: (
formId: string,
callback: (event: CustomEvent<[string, any, string]>) => void
) => void;
setSpecInPath: (
formId: string,
path: string,
newSpec: Field
) => void;
};
}
}
export {};