Parent Event Notifications (ProcessFactorial Forms)
ProcessFactorial Forms can notify a host page about navigation and interaction events using window.parent.postMessage.
Message format
The current event payload uses a structured host message. When a ForwardToHost pipeline event fires, the message may also include a data object containing field values configured by the form designer.
window.parent.postMessage(
{
type: 'processfactorial:host-event',
source: 'processfactorial.forms',
context: {
pageId: pageId,
formToken: formToken,
fieldId: fieldId,
event: {
type: event?.type || null,
autoTriggered: !!event?.autoTriggered,
defaultAction: event?.defaultAction || null,
isTrusted: !!event?.isTrusted,
customButtonClick: !!event?.customButtonClick,
customButtonPhase: event?.customButtonPhase || null,
customButtonFieldId: event?.customButtonFieldId || null,
customButtonClasses: event?.customButtonClasses || '',
pipelineHandledNavigation: !!event?.pipelineHandledNavigation
},
cause: cause,
target: {
tagName: tagName,
id: id,
dataId: dataId,
className: className
},
timestampUtc: new Date().toISOString()
},
// When a ForwardToHost event fires, the selected field values are included here
data: {
"fieldId1": "value1",
"fieldId2": "value2"
}
},
targetOrigin
);
The previous payload type pfEventTriggerNotification can still be handled for backward compatibility.
Prerequisites
Warning
For production, use a specific target origin instead of * when posting messages, and validate event.origin in your host listener.
- The ProcessFactorial Forms scripts must be loaded and running.
- If embedded in an iframe, the host page must attach a message event listener.
- The host page should validate event.origin and event.data.type.
Quick start (copy/paste example)
Paste this in the host page (parent window) that embeds or renders ProcessFactorial Forms.
Common things you can do inside the handler:
- React to navigation (e.g. show or hide host UI elements when the user moves between form pages)
- Detect when a specific field changes and respond in the host (e.g. update a summary panel, enable a host-side button, or pre-load data from another service)
- Intercept default actions such as Submit or Cancel to coordinate host-side behaviour before or after the form acts
- Block or gate a step by reading
defaultActionand deciding whether to proceed - Forward events to a telemetry or logging service to record user interactions
const allowedOrigins = new Set([
'https://your-form-host.example'
]);
function normalizeHostMessage(data) {
if (!data || typeof data !== 'object') return null;
if (data.type === 'processfactorial:host-event') {
const context = data.context || {};
return {
channel: 'processfactorial:host-event',
pageId: context.pageId || null,
eventType: context.event && context.event.type ? context.event.type : 'unknown',
defaultAction: context.event && context.event.defaultAction ? context.event.defaultAction : null,
autoTriggered: !!(context.event && context.event.autoTriggered),
isTrusted: !!(context.event && context.event.isTrusted),
cause: context.cause || null,
fieldId: context.fieldId || null,
formToken: context.formToken || null,
customButtonClick: !!(context.event && context.event.customButtonClick),
customButtonPhase: context.event && context.event.customButtonPhase ? context.event.customButtonPhase : null,
customButtonFieldId: context.event && context.event.customButtonFieldId ? context.event.customButtonFieldId : null,
customButtonClasses: context.event && context.event.customButtonClasses ? context.event.customButtonClasses : '',
pipelineHandledNavigation: !!(context.event && context.event.pipelineHandledNavigation),
target: context.target || null,
timestampUtc: context.timestampUtc || null,
source: data.source || null
};
}
// Backward compatibility
if (data.type === 'pfEventTriggerNotification') {
return {
channel: 'pfEventTriggerNotification',
pageId: data.pageId || null,
eventType: data.event && data.event.type ? data.event.type : 'unknown',
defaultAction: data.event && data.event.defaultAction ? data.event.defaultAction : null,
autoTriggered: !!(data.event && data.event.autoTriggered),
isTrusted: !!(data.event && data.event.isTrusted),
cause: null,
fieldId: null,
formToken: null,
customButtonClick: false,
customButtonPhase: null,
customButtonFieldId: null,
customButtonClasses: '',
pipelineHandledNavigation: false,
target: null,
timestampUtc: null,
source: null
};
}
return null;
}
window.addEventListener('message', function (event) {
if (!allowedOrigins.has(event.origin)) return;
const normalized = normalizeHostMessage(event && event.data ? event.data : null);
if (!normalized) return;
// Example: react to form navigation — update host UI when the user moves to a new page
if (normalized.cause === 'navigation' || normalized.defaultAction === 'TriggerDefaultNext') {
// e.g. updateHostSidebar(normalized.pageId);
}
// Example: react to a specific field change — respond in the host when a key field is updated
if (normalized.eventType === 'change' && normalized.fieldId === 'your-field-id') {
// e.g. refreshHostPanel(normalized.fieldId);
}
// Example: detect a submit action before the form completes
if (normalized.defaultAction === 'TriggerDefaultSubmit') {
// e.g. notifyHostSystem();
}
// forward to a telemetry or logging service
// myTelemetry.track('processfactorial-forms-event', {
// channel: normalized.channel,
// source: normalized.source,
// pageId: normalized.pageId,
// eventType: normalized.eventType,
// defaultAction: normalized.defaultAction,
// autoTriggered: normalized.autoTriggered,
// cause: normalized.cause,
// fieldId: normalized.fieldId,
// timestampUtc: normalized.timestampUtc,
// origin: event.origin
// });
});
How to use parent notifications (recommended pattern)
- Register a message listener in the host.
- Validate sender origin and payload shape.
- Normalize both processfactorial:host-event and pfEventTriggerNotification payloads.
- Branch host behavior by eventType, defaultAction, and cause.
- Keep handler work lightweight and resilient.
Custom button notification behavior
Custom button notifications are sent through the same processfactorial:host-event channel and are explicitly marked so the host can distinguish them from normal field or navigation updates.
- customButtonClick: true when the message was emitted by custom button orchestration
- cause: custom-button-click for custom button notifications
- customButtonClasses: class list of the clicked button/control
- customButtonFieldId: orchestration field id used for the button pipeline
- customButtonPhase: before or after
- pipelineHandledNavigation: true if the OnButtonClick pipeline already handled navigation
Timing is configurable on the button (or nearest ancestor) using data-host-notify-phase:
- before: notify host before OnButtonClick pipeline runs
- after: notify host after OnButtonClick pipeline runs (default)
- both: notify host both before and after
API reference (consumer view)
Common normalized fields
- channel: processfactorial:host-event | pfEventTriggerNotification
- source: processfactorial.forms | null
- pageId: string | null
- formToken: string | null
- eventType: click | change | input | submit | unknown | other browser/custom event types
- autoTriggered: true | false
- isTrusted: true | false
- defaultAction: TriggerDefaultNext | TriggerDefaultPrevious | TriggerDefaultSave | TriggerDefaultSubmit | TriggerDefaultCancel | null
- cause: custom-button-click | default-action | auto-triggered | field-update | navigation | unknown | event type fallback
- fieldId: string | null
- customButtonClick: true | false
- customButtonPhase: before | after | null
- customButtonFieldId: string | null
- customButtonClasses: string
- pipelineHandledNavigation: true | false
- target: object | null
- timestampUtc: ISO-8601 UTC string | null
Possible values details
- channel
- processfactorial:host-event: current structured payload
- pfEventTriggerNotification: legacy payload
- eventType
- Common values: click, change, input
- Can be any browser event type or custom event type emitted by the form runtime
- unknown is used by host normalization when event.type is missing
- cause
- custom-button-click: custom button notification emitted by button orchestration
- default-action: event.defaultAction is present
- auto-triggered: event.autoTriggered is true
- field-update: event.type is input or change
- navigation: click on navigation link
- unknown: no detectable cause
- fallback: raw event type string
- defaultAction
- TriggerDefaultNext
- TriggerDefaultPrevious
- TriggerDefaultSave
- TriggerDefaultSubmit
- TriggerDefaultCancel
- null when not a default action event
- target
- tagName: lowercase tag name, for example input or div
- id: DOM id of changed control when available
- dataId: data-id attribute when available
- className: class list string for the matched control
Current payload (full processfactorial:host-event schema)
- data.type: processfactorial:host-event
- data.source: processfactorial.forms
- data.context.pageId: string | null
- data.context.formToken: string | null
- data.context.fieldId: string | null
- data.context.event.type: string | null
- data.context.event.autoTriggered: boolean
- data.context.event.defaultAction: string | null
- data.context.event.isTrusted: boolean
- data.context.event.customButtonClick: boolean
- data.context.event.customButtonPhase: before | after | null
- data.context.event.customButtonFieldId: string | null
- data.context.event.customButtonClasses: string
- data.context.event.pipelineHandledNavigation: boolean
- data.context.cause: string | null
- data.context.target.tagName: string | null
- data.context.target.id: string | null
- data.context.target.dataId: string | null
- data.context.target.className: string
- data.context.timestampUtc: string | null
- data.data: object | null (present when emitted by a ForwardToHost event)
Legacy payload
- data.type: pfEventTriggerNotification
- data.pageId: string | null
- data.event.type: string | null
- data.event.autoTriggered: boolean
- data.event.defaultAction: string | null
Common trigger points
- Navigation via tab clicks
- Default Next and Previous actions
- Cancel, Save, and Submit actions
- Field change and input updates when host notification is enabled for that control
- Custom button orchestration events (FormField, FormNavigation, TileNavigation, Modal) using OnButtonClick pipelines
Form ready signal (processfactorial:formReady)
In addition to the postMessage channel, ProcessFactorial Forms dispatches a DOM CustomEvent when the form is fully initialised on the current page. This is useful for host applications that run in the same window as the form (not an iframe) and need to know when it is safe to interact with fields, read values, or inject data.
The signal fires after:
- The form HTML has been rendered
- Any existing record data has been loaded and applied to fields
- Display rules have been evaluated
- All lookup dropdowns on the active page have finished fetching their data
Next-page prefetch lookups are excluded by design — the signal covers the page the user sees, not background caching.
whenFormReady() vs whenReady()
The Forms Engine exposes two readiness methods on window.pfFormsEngine.v1:
| Method | Resolves when | Use for |
|---|---|---|
api.whenReady() |
Core handlers are wired (HTML and data fetched) | Basic DOM readiness checks |
api.whenFormReady() |
All of the above plus active-page lookup dropdowns populated | Safe to call setValue, getValue, InjectHTML without race conditions |
If your logic reads or writes field values, always prefer whenFormReady(). Using whenReady() alone can cause race conditions where lookup dropdowns are still loading and their auto-triggered initialisation events interfere with values you set.
Async API — via the Forms Engine (recommended)
const api = window.pfFormsEngine?.v1;
// Returns a Promise that resolves when the form is fully ready.
// Safe to await; resolves immediately if already ready.
await api.whenFormReady();
// All fields, values, and lookups on the active page are now settled.
// Safe to call getValue, setValue, InjectHTML.
Event listener API
document.addEventListener('processfactorial:formReady', function () {
// Form is fully initialised on the active page.
// Safe to call getValue, setValue, InjectHTML.
}, { once: true });
Notes
processfactorial:formReadyis a DOM CustomEvent dispatched ondocument. It does not travel through the postMessage channel.- It fires once per form load. If the user navigates between form pages, it does not re-fire — page navigation is covered by the
processfactorial:host-eventpostMessage channel. - A failed lookup fetch will not prevent the signal from firing;
Promise.allSettledsemantics are applied internally.
Notes and best practices
- If no host listener is registered, events are posted with no host-side effect.
- The callback runs in the parent window context.
- Treat notifications as telemetry hints rather than transactional guarantees.
- Keep host side effects idempotent because users can trigger events rapidly.
ForwardToHost event (data payload)
The ForwardToHost event type (DEMEventTypes.ForwardToHost) can be added to any pipeline in the form designer. When triggered, it collects specified field values from the current form state and includes them in the data property of the processfactorial:host-event postMessage.
Use case: pass form data to the host
A form designer can add a ForwardToHost event to a button's OnButtonClick pipeline and configure which field values to send. The host listens for the processfactorial:host-event message and reads the data property.
Host-side usage
window.addEventListener('message', function (event) {
if (event.data && event.data.type === 'processfactorial:host-event') {
const msg = event.data;
// If this ForwardToHost event includes field data...
if (msg.data) {
// msg.data contains the field values configured by the form designer
// For example: { "countryName": "Australia", "isoCode": "AU" }
console.log('Received form data from DEM:', msg.data);
// Store or process the data as needed
// e.g. window._storedJwtToken = msg.data.jwtToken;
}
}
});
Notes
- The
dataproperty is only present when a ForwardToHost pipeline event fired. - Existing host listeners that do not check for
dataare unaffected. - For production, use a specific
targetOrigin(configured vianpoLib.hostPostMessageOrigin) instead of*.
Troubleshooting
-
Events never arrive
- Confirm the host registered window.addEventListener('message', ...).
- Confirm your allow-list includes the form origin.
-
pageId is empty or unexpected
- The active page may not yet be established in the current flow.
- Validate navigation timing in host logs.
-
Event details vary by action
- Handle both native and synthetic event shapes defensively.
- Add null checks before reading nested properties.