Embedded Content Blocks
Embedded Content blocks let a host page inject custom HTML/UI into a form at runtime.
What is an “Embedded Content block”?
An Embedded Content block is a placeholder field in the form that you can replace with custom HTML at runtime.
Typical use cases:
- Render a bespoke UI component (for example: a mini widget, summary card, address or map control, or custom buttons)
- Collect user input in a custom UI and sync it into a normal form field (so it saves/submits normally)
- Add contextual information (read-only hints, data pulled from your host application)
Under the hood, the host page calls window.pfFormsEngine.v1.InjectHTML(...) which replaces the content of the target field’s wrapper.
Prerequisites
Warning
Any changes to the ProcessFactorial Form DOM or changes to the data must be done via the pfFormsEngine.v1 api.
Any other manipulations, whether DOM or data, are explicitly not supported.
- The form must already be embedded/loaded on the page.
- Your page must be able to run JavaScript in the same window as the embedded form.
- You need the
fieldIdof:- the placeholder field you will inject into, and
- any real form fields you want to read/write via
getValue/setValue.
Quick start (copy/paste example)
Paste this into the browser devtools console on a page where the form is already injected.
Note: many modern browser devtools support top-level await. If yours doesn’t, wrap the example in an async IIFE.
const fieldIdControl = '10000000-f300-0000-0000-000000000001'; // placeholder field we will inject HTML into
const fieldIdName = '10000000-9900-0000-0000-000000000001'; // normal string field we will write data to
const api = window.pfFormsEngine && window.pfFormsEngine.v1;
if (!api) {
throw new Error('pfFormsEngine.v1 not found on window');
}
// Wait for the form to finish initialization
const ready = await api.whenReady(15000);
if (!ready) {
console.warn('Form not ready (contentLoaded not seen)');
}
// 1) Set a value in a “real” form field
api.setValue(fieldIdName, 'Hello from pfFormsEngine.v1');
// 2) Read it back
console.log('getValue:', api.getValue(fieldIdName));
// 3) Inject HTML (button + static text + input)
const wrapperEl = api.InjectHTML(fieldIdControl, `
<div class="card border-secondary">
<div class="card-body p-2">
<div class="d-flex align-items-center justify-content-between mb-2">
<div class="fw-semibold">Injected content</div>
</div>
<div class="input-group input-group-sm">
<input id="pf_input" type="text" class="form-control" value="" placeholder="Type here…" />
<button id="pf_btn" type="button" class="btn btn-primary">Show alert</button>
</div>
<div class="form-text mt-1">This value will be saved as part of the form</div>
</div>
</div>
`);
// InjectHTML normally returns the wrapper HTMLElement.
// If it cannot find the target wrapper, it returns an object like: { injected:false, reason:'...' }
if (!wrapperEl || typeof wrapperEl.querySelector !== 'function') {
console.warn('InjectHTML failed:', wrapperEl);
}
// Wire events on injected DOM
const inputEl = wrapperEl.querySelector('#pf_input');
const btnEl = wrapperEl.querySelector('#pf_btn');
// Button click: show an alert with the current injected input value
btnEl.onclick = function () { alert(inputEl.value); };
// Input change: sync into the form via setValue
// Tip: suppressPipelines can be useful for simple “mirror” updates.
inputEl.addEventListener('input', function () {
api.setValue(fieldIdName, inputEl.value, null, null, { suppressPipelines: true });
});
How to use Embedded Content blocks (recommended pattern)
-
Wait for the form to be ready
- Call
await pfFormsEngine.v1.whenReady(timeoutMs).
- Call
-
Inject HTML into the placeholder field
- Call
pfFormsEngine.v1.InjectHTML(placeholderFieldId, html). - Keep your injected HTML self-contained.
- Avoid
idcollisions: anyid="..."inside your injected HTML must be unique on the page.
- Call
-
Wire your own event handlers
- Injecting HTML replaces DOM nodes, so any previous handlers inside that wrapper are gone.
- You are responsible for wiring click/input handlers for your injected elements.
-
Sync data into real form fields
- Use
setValue(fieldId, value)to write values into the form engine so saves/submits work. - Use
getValue(fieldId)to read form data (including display-friendly values).
- Use
-
Restore / cleanup when needed
- Use
RestoreHTML(fieldId)to revert the field to its previous markup.
- Use
API reference (consumer view)
All functions below are available on window.pfFormsEngine.v1.
Readiness
whenReady(timeoutMs = 10000) : Promise<boolean>- Resolves
trueonce the form has fired its internal “ready” signal. - Resolves
falseif it times out.
- Resolves
Read/write form values
-
getValue(fieldId, groupCode = null, groupIndex = null)- Returns
{ value, friendlyValue, properties, fieldId, effectiveFieldId, groupCode, groupIndex }. friendlyValuemay come from field display metadata (for example, lookups).
- Returns
-
setValue(fieldId, value, groupCode = null, groupIndex = null, options = null)- Writes to the form engine and updates any matching DOM input(s) when present.
options.triggerEvents(defaulttrue): dispatches a syntheticinputevent on the primary input.options.suppressPipelines(defaultfalse): temporarily prevents pipeline events while updating the value.
Embedded Content helpers
-
InjectHTML(fieldId, htmlContent, groupCode = null, groupIndex = null, options = null)- Replaces the closest
.npo-fieldwrapper content for the target field. - Default behavior: refreshes conditional/visibility maps after injection.
options.refreshMaps(defaulttrue): set tofalseif you are making many DOM changes and will callrefreshConditionalMaps()once at the end.- Returns:
- the wrapper
HTMLElementon success, or - an object like
{ injected:false, reason:'Field wrapper not found', fieldId, groupCode, groupIndex }on failure.
- the wrapper
- Security:
htmlContentis inserted as-is. Only inject trusted HTML.
- Replaces the closest
-
GetHTML(fieldId, groupCode = null, groupIndex = null)- Returns
{ found:true, html:'...' }on success, or{ found:false, reason:'...' }.
- Returns
-
RestoreHTML(fieldId, groupCode = null, groupIndex = null, options = null)- Restores the previous wrapper HTML captured by the most recent
InjectHTMLcall. options.refreshMaps(defaulttrue).
- Restores the previous wrapper HTML captured by the most recent
-
refreshConditionalMaps()- Rebuilds conditional/required/editable maps and re-evaluates visibility.
- Useful if you injected content that impacts layout or conditional display.
Trigger configured form events (advanced)
triggerEvent(fieldIdOrEventId, eventIdOrNull, options)- Triggers configured events using the same orchestration used by UI clicks.
- Important behavior:
- Does not filter by
EventPipeline(it will execute all events for the key unless you specify an event id/type filter). - By default it skips events where
IsActive === false.
- Does not filter by
- Common options:
pipeline(default'OnButtonClick'): sets the pipeline context.eventId: trigger a single event by id.eventType: trigger only certain event types.includeInactive: settrueto include inactive events.
Notes, limitations, and best practices
- Prefer storing “real” data in normal form fields via
setValue.- Injected inputs are not automatically persisted unless you mirror them into a real field.
- Don’t inject untrusted HTML.
- Don't inject any script elements. Instead wire up the scripted events via the returned element when calling
InjectHTML(...) - Avoid duplicate DOM ids in injected markup.
- If you inject content repeatedly, consider
options.refreshMaps: falseand callrefreshConditionalMaps()once at the end for performance.
Troubleshooting
-
pfFormsEngine.v1 not found on window- The form scripts may not be loaded yet, or the form is not embedded on this page.
-
whenReady(...)returnsfalse- The form may not have finished loading, or an error prevented initialization.
-
InjectHTMLfails with “Field wrapper not found”- Verify you are using the correct placeholder
fieldId. - If the field exists inside a repeating/grouped section, pass the correct
groupCodeandgroupIndex.
- Verify you are using the correct placeholder