Interactive Dynamic XFA Widget Runtime
TXFAWidgetRuntime provides a host-neutral interaction model for dynamic XFA forms without converting them into AcroForm fields or flattening their content
The runtime exposes deterministic widget bounds and state so a desktop, service, or custom renderer can supply its own input, painting, accessibility, and event loop
Create a runtime
Create the runtime directly from XDP bytes or call THotPDF.CreateLoadedXFAWidgetRuntime after loading a PDF whose AcroForm contains a single-stream or packet-array /XFA entry
var
Runtime: TXFAWidgetRuntime;
State: TXFAWidgetState;
begin
Runtime := PDF.CreateLoadedXFAWidgetRuntime;
try
if (Runtime <> nil) and (Runtime.WidgetCount > 0) then
begin
State := Runtime.Widgets[0];
Runtime.FocusWidget(State.ID);
Runtime.BeginEdit(State.ID);
Runtime.ReplaceSelection(0, Length(State.Value), 'Updated value');
if not Runtime.CommitEdit then
raise Exception.Create(Runtime.LastDiagnostic);
end;
finally
Runtime.Free;
end;
end;
The loaded-document factory is read-only with respect to the PDF object graph and existing XFAFlattenWarnings; saving the document retains its original /XFA and /NeedsRendering entries
Interaction model
FocusWidgetandClearFocusmaintain one focused widget, execute enter/exit scripts and publish ordered callbacks after commitmentBeginEdit,ReplaceSelection,CancelEdit, andCommitEditpreserve Unicode text including leading and trailing spacesHitTestmaps page coordinates to the topmost visible widgetOnWidgetInvalidated,OnLayoutChanged, andOnFocusChangedlet the host refresh only affected state
Interactive commits synchronise existing native scalar values in hidden and invisible aliases, including actual repeated data scopes and byte-exact rollback
Atomic validation and calculation
A commit resolves explicit SOM bindings and the current repeated-row data context before running validate and calculate scripts
The edited value, calculated values, data nodes, widget model, focus and edit state, warnings, and pass counters publish together only after validation and layout stabilise
Rejected scripts, exhausted budgets, invalid UTF-16 selection boundaries, and layout or host-measurement exceptions restore the complete previous state and set LastDiagnostic
Budgets
TXFAWidgetRuntimeOptions limits widget count, edited value length, calculation passes, reflow passes, layout operations, script operations, elapsed script time, and other FormCalc or JavaScript resources
The widget cap applies whilst layout items and pagination fragments are appended, whilst loaded XFA decompression and packet assembly stop at the XFA DOM input limit before parsing
MaxLayoutOperations defaults to 200000 and bounds both the document walk and layout work; layout depth is limited to 128, event target and instance-initialisation depth to 64, and recursive event dispatch is rejected
Default literal dynamic events
DispatchEvent accepts semicolon-separated literal operations in matching field event scripts with FormCalc or JavaScript content types; these operations are parsed directly and do not require a JavaScript DLL
row.instanceManager.addInstance(true);
row.instanceManager.removeInstance(0);
target.presence = "hidden";
addInstance appends one instance and accepts true, false, 1, or 0 as its merge argument; an omitted argument uses true, and the shorthand _row.addInstance(1) is also accepted
The supported model binds every existing same-name data group, so a new instance receives a new data group initialised from the template defaults for either merge argument; instance creation does not clone the previous row's entered values
removeInstance uses a zero-based integer index amongst the selected subform's same-name data groups; the subform must have an occur element with a repeatable maximum, and both operations enforce its minimum and maximum, including max="-1"
Instance mutation requires implicit named dataset binding and ASCII XML data names; explicit data-reference binding and ambiguous parent contexts are rejected before publishing the transaction
Targets resolve through named template children in the event's enclosing scopes, including dotted paths and this.parent; a manager nested inside a repeated scope uses the event's own data group
presence accepts visible, hidden, and invisible on template fields, draws, subforms, and exclusion groups; hidden content occupies no flow space, whilst invisible content retains its space and is omitted from widgets and flatten output
With the default literal parser, presence changes apply to the selected template node and therefore all its repeated occurrences; indexed per-instance presence, inactive, arbitrary expressions, variables, conditionals, loops, and other event-script operations require an additional event host
An event batch publishes data changes, calculations, layout, focus, and editing state together after reflow stabilises; any rejected operation or exhausted input, operation, instance, value, widget, layout, or elapsed-time budget restores the complete previous document and interaction state
Removing a row preserves focus and pending edits for surviving data groups even when their widget indices change; removing or concealing the focused widget clears focus and publishes the corresponding focus callback after success
General JavaScript and FormCalc event execution
Set TXFAWidgetRuntimeOptions.ScriptOptions.EnableJavaScript to enable the shipped bounded QuickJS bridge for event execution; JavaScript events then support functions, closures, arrays, conditions, loops and exceptions rather than the default literal grammar
Options := TXFAWidgetRuntimeOptions.Default;
Options.ScriptOptions.EnableJavaScript := True;
Runtime := TXFAWidgetRuntime.Create(XDPBytes, 595, 842, Options);
The event host exposes this, enclosing named fields and subforms, parent, writable rawValue, presence and access, instanceManager.count/min/max, addInstance, removeInstance, insertInstance, moveInstance and setInstances, xfa.resolveNode, xfa.resolveNodes, node-level resolution and xfa.layout.relayout
Node lists support numeric indexing, length and item; repeated field values retain their own dataset targets, and SOM paths accept named children with numeric or wildcard indices for live host nodes
const values = xfa.resolveNodes("main.row[*].amount[*]");
let total = 0;
for (const field of values) total += Number(field.rawValue);
xfa.resolveNode("main.total").rawValue = total;
if (total > 100) this.parent.warning.presence = "visible";
Each repeated subform has its own live object and child fields; index, parent child getters and subsequent SOM queries follow insert, move and remove operations immediately inside the script
addInstance and insertInstance return a live subtree initialised from template defaults, so the script can write its field values before the event publishes; removed handles reject subsequent reads or writes
General script presence and access changes persist in the standard XFA form packet per occurrence, whilst field values and repeat operations persist in datasets; insert, move and remove keep the corresponding form state aligned with the data groups
With the same opt-in, FormCalc event scripts compile to the bounded engine and support var, if/elseif/else, for with upto or downto, while, foreach, func with implicit results, arithmetic and comparisons, string concatenation, wildcard node aggregation and implicit field-value assignment
The event function library includes Sum, Count, Avg, Min, Max, Round, common math functions, string slicing, case conversion, trimming, replacement, HasValue, Exists, Within, Oneof and Choose; built-in names are case insensitive, At(source, search) follows XFA argument order including empty-search behaviour, financial functions follow XFA parameter order, and the date and finance catalogues are described in FormCalc Functions
Script mutations are recorded inside the engine, validated and replayed inside the runtime transaction; exceptions, interruption, invalid Unicode, invalid host operations and exhausted budgets publish no partial document changes
General script execution remains off by default, uses no browser or external process, and exposes no filesystem, network or application APIs; the caller may supply JavaScriptEvaluator instead of the bridge
Persistent script objects
JavaScript script objects inside a subform's variables element expose their variables and functions through the script name; lexical variables, object state and nested closures remain alive for the runtime session, with normal lexical shadowing and strict directives preserved
<variables>
<script name="Helpers" contentType="application/x-javascript"><![CDATA[
let count = 0;
const nextPrivate = (() => { let value = 0; return () => ++value; })();
function next() { return ++count + ":" + nextPrivate(); }
]]></script>
</variables>
Event code can call Helpers.next(); repeated subform occurrences own independent script objects, and functions resolve named fields against the current live form context
Captured field nodes and managers follow stable data identity across moves and publication of newly created instances; accessing a removed node rejects the event and rolls back its module state
General calculate and validate scripts share the session and script objects; JavaScript supports implicit completion values and explicit return, whilst FormCalc returns its final expression
Failed transactions restore lexical and closure state by replaying the committed virtual journal, with recorded time and random inputs; replay and current execution share the transaction deadline, and the default journal limits are 64 MiB of scripts/results and 8192 entries
Script-object state lives in the runtime session and is initialised again after XDP reload; standard document values and form overrides continue to persist through SaveToBytes
Custom JavaScriptEvaluator callbacks retain the existing native calculate/validate envelope; the persistent session is supplied by the shipped engine
Configure explicit host transport
Set HostTransport and optional OnHostTransactionCompleted in TXFAWidgetRuntimeOptions to execute host dialogs, printing, navigation, submission and data transfer through application callbacks
Explicit XFA Host Transport supplies xfa.host.messageBox, response, beep, print, gotoURL, submitForm, importData, exportData and FormCalc Get, Post and Put; the application returns typed results and controls external effects
Retries and session replay use recorded responses, so callbacks execute once per dispatched request; failure completion lets the application discard staged effects or compensate reversible operations
Positive HostTransportLimits bound request count, argument count and aggregate request/response bytes across the enclosing runtime transaction
Locale and picture execution
General FormCalc events, calculate and validate scripts support locale and picture functions, including Format, Parse, localised date/time conversions, units, encoding, UUIDs and English number words
Dynamic FormCalc Eval executes dynamically supplied calculations with isolated variables and functions, relative field access, native compilation, recorded responses and transactional rollback
Explicit FormCalc references support write-through assignment, rebind, null detachment, function arguments and stable handles retained by script objects across repeated-instance movement
Live SOM paths support inferred, absolute and relative occurrence indices, class and descendant selectors, transparent containers, candidate predicates and direct FormCalc indexed assignment
The native Data DOM exposes datasets through $data and $record, synchronises bound form reads and writes immediately, mirrors repeated instances and preserves retained data references through native identity publication
The native Property DOM exposes declared value, font, UI and other property children, synchronises typed field values and persists per-instance attributes used by font measurement and supported flatten styling
The executing node inherits its nearest locale attribute; document localeSet entries override system symbols and patterns, whilst computed locale names are resolved through bounded internal requests and preserved in the session journal
Num2Date without a picture now uses the ambient default date pattern; supply YYYY-MM-DD explicitly when ISO output is required
Stateful host model
XFA Host Model supplies application metadata, actual page count, page navigation, Unicode title, calculation/validation flags, scoped reset and field focus with transactional enter/exit lifecycle
TXFAWidgetRuntimeOptions.HostModel configures initial application state; TXFAWidgetRuntime.HostModel returns current state, and failed events restore it together with document and interaction snapshots
A focus move with no enter/exit script, pending edit or initialised lifecycle changes focus without starting script calculation or deadline processing; scripted focus and pending edit commits still use transactional budgets, validation and recalculation
Advanced picture clauses execute through the same runtime transactions, locale retry journal and calculation lifecycle, with bounded compound parsing
Initialize and maintain the lifecycle
Call InitializeForm after configuring the runtime and callbacks to execute initialise events in template order, then calculate and validate, form-ready events and layout-ready events; the call is idempotent after success
New instances created by initialisation or ready events receive their own initialise events before their ready handlers; layout-ready execution repeats only when layout changes, within the reflow and transaction budgets
After initialisation, successful event dispatch and edit commit initialise newly created instances and execute calculate, validate and layout-ready; existing occurrences retain their initialisation state across moves and transaction rollback
Lifecycle failures roll back document bytes, widget interaction and initialisation bookkeeping together, and callbacks publish only after the enclosing transaction succeeds
Save the updated form
SaveToBytes returns the runtime's complete updated XDP, including modified datasets and standard per-instance form state; literal-parser presence changes remain template attributes; use these bytes with SetXFADocument when writing a PDF or with HPDFXFAFlatten for the updated flatten output
The loaded-document factory creates an independent runtime, so runtime events do not automatically modify the source PDF's object graph
Optional instance presentation profile
Dynamic XFA Instance Presentation adds explicit repeated bindings, stable per-instance IDs, indexed presence and access, bounded conditional events, host canvas painting, and accessibility through an independent helper; the default runtime and its restricted event grammar remain compatible
UpdateDocument provides transactional document actions; optional runtime property, binding-scope, identity, resolution-started, and document-validation callbacks support the helper whilst nil defaults preserve existing callers
Lower-level event and form APIs
HPDFXFACompileFormCalcEvent compiles event syntax; HPDFXFAExecuteJavaScriptEvent executes the virtual context described by TXFAEventBinding and TXFAEventBindings
TXFAJavaScriptSession supplies persistent execution and bounded checkpoints for lower-level callers
Returned TXFAEventAction values use TXFAEventActionKind; transactional replay is supplied by the runtime
HPDFXFAResolveFormInstanceNode and HPDFXFAResolveFormInstanceProperty provide bounded standard form-packet lookup
Current boundaries
The runtime is a single-thread host object; GUI and painting remain the responsibility of the host or the optional presentation helper
The supported host state, live form DOM and lifecycle do not yet cover every Acrobat host property, alternate-era and localised digit pictures, synchronous dynamic reflow queries, or every SOM operator
See Bounded XFA FormCalc and JavaScript, XFA Packet DOM, and Interactive Document Processing