Skip to content

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 fieldId of:
    • 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 });
});
  1. Wait for the form to be ready

    • Call await pfFormsEngine.v1.whenReady(timeoutMs).
  2. Inject HTML into the placeholder field

    • Call pfFormsEngine.v1.InjectHTML(placeholderFieldId, html).
    • Keep your injected HTML self-contained.
    • Avoid id collisions: any id="..." inside your injected HTML must be unique on the page.
  3. 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.
  4. 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).
  5. Restore / cleanup when needed

    • Use RestoreHTML(fieldId) to revert the field to its previous markup.

API reference (consumer view)

All functions below are available on window.pfFormsEngine.v1.

Readiness

  • whenReady(timeoutMs = 10000) : Promise<boolean>
    • Resolves true once the form has fired its internal “ready” signal.
    • Resolves false if it times out.

Read/write form values

  • getValue(fieldId, groupCode = null, groupIndex = null)

    • Returns { value, friendlyValue, properties, fieldId, effectiveFieldId, groupCode, groupIndex }.
    • friendlyValue may come from field display metadata (for example, lookups).
  • 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 (default true): dispatches a synthetic input event on the primary input.
    • options.suppressPipelines (default false): temporarily prevents pipeline events while updating the value.

Embedded Content helpers

  • InjectHTML(fieldId, htmlContent, groupCode = null, groupIndex = null, options = null)

    • Replaces the closest .npo-field wrapper content for the target field.
    • Default behavior: refreshes conditional/visibility maps after injection.
    • options.refreshMaps (default true): set to false if you are making many DOM changes and will call refreshConditionalMaps() once at the end.
    • Returns:
      • the wrapper HTMLElement on success, or
      • an object like { injected:false, reason:'Field wrapper not found', fieldId, groupCode, groupIndex } on failure.
    • Security: htmlContent is inserted as-is. Only inject trusted HTML.
  • GetHTML(fieldId, groupCode = null, groupIndex = null)

    • Returns { found:true, html:'...' } on success, or { found:false, reason:'...' }.
  • RestoreHTML(fieldId, groupCode = null, groupIndex = null, options = null)

    • Restores the previous wrapper HTML captured by the most recent InjectHTML call.
    • options.refreshMaps (default true).
  • 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.
    • Common options:
      • pipeline (default 'OnButtonClick'): sets the pipeline context.
      • eventId: trigger a single event by id.
      • eventType: trigger only certain event types.
      • includeInactive: set true to 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: false and call refreshConditionalMaps() 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(...) returns false

    • The form may not have finished loading, or an error prevented initialization.
  • InjectHTML fails 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 groupCode and groupIndex.