Technical Article

XFA Binding Filters: Which Data Record Does the Field Use?

A purchase form contains several XFA detail records, yet its total field displays a value from the wrong-looking row. HotPDF evaluates the binding filter in data order: a scalar field takes the first matching node, while a repeated group can use the matches in their original order

Start with the form's data, not the visible field

Three parts of an XFA document participate in this example. The Template describes the field and its binding, the datasets packet holds the candidate records, and the Form reflects the resulting layout and bindings. Our receipt contains three detail totals: 120, 250 and 300. A field asking for the first total greater than 200 selects 250 from that ordering. Swap the last two records and 300 becomes the first match. The field has not suddenly changed its arithmetic; the order of the data it is asked to search has changed. Check that ordering if you find the form is correct for one receipt but selects a different value for another

Read the filter in the binding path

This fragment belongs to a Template field inside a complete XDP document. The greater-than character is escaped because the predicate sits in an XML attribute; an XML parser supplies the decoded operator to the expression

<field name="selected">
  <bind match="dataRef"
    ref="$data.Receipt.Detail.(Total_Price.rawValue &gt; 200).Total_Price"/>
  <ui><textEdit/></ui>
  <value><text/></value>
</field>

The punctuation identifies the scripting language. A .(...) filter uses JavaScript, while .[...] uses FormCalc. Each candidate is evaluated in its data context, and when a path contains multiple filter stages, the next stage receives the surviving candidates after the preceding stage has completed. With the three receipt details in their original order, 120 fails the predicate and 250 is the first value above 200. A scalar field therefore selects the second detail. That result depends on the order of the nodes in the dataset, so changing how the incoming XML is assembled can alter the field's displayed value even when the binding expression stays exactly the same

<Receipt>
  <Detail><Total_Price>120</Total_Price></Detail>
  <Detail><Total_Price>250</Total_Price></Detail>
  <Detail><Total_Price>300</Total_Price></Detail>
</Receipt>

The dataset needs the Receipt, Detail and Total_Price elements referenced by the path, under the form's xfa:data element. Put 300 before 250 and the first matching value becomes 300. When looking at the source XML, expect the attribute to contain &gt;; a debugger displaying the parsed attribute can show the decoded greater-than sign instead. Those two views can describe the same predicate. A malformed attribute is a different problem: the form may fail to parse before the filter has any candidates to evaluate, so confirm the XML is well formed before reasoning about which value the expression should select

Which matching record does a field select?

A scalar field and a repeated group consume the result differently. The scalar binding chooses the first accepted node; a group designed for repeated matches can use both 250 and 300 in their data order. One displayed scalar value does not establish that only one record matched. An edit after binding introduces another stage. If the chosen data node is removed, the existing scalar selection does not silently advance to the next accepted node. An explicit remerge establishes bindings against the changed data again. Record the value after you make the edit and again after the remerge—those observations describe two different moments in the form's lifetime, and combining them can make an ordinary change of binding look like a filter-order problem

What happens when a filter changes data?

A predicate can also change data while it evaluates candidates. In that case an earlier evaluation may alter what a later candidate sees, making the evaluation order part of the form's behavior. HotPDF completes the current query and keeps its accepted choices when a data edit requires the remaining binding work to be planned again; completing one filter does not cause that same filter to be run a second time merely because the data changed. This is a good reason to keep deliberate mutations visible in the form design. A displayed total alone cannot tell us which candidates were visited or which of them changed the dataset during the query

A predicate can answer one question: does this candidate match? Put an intentional update in a named event when the form design permits it, and the binding expression becomes easier to understand without tracing side effects through every candidate. Some forms do require an updating predicate. For those, keep the dataset small while debugging and record the candidate order together with the data values each evaluation sees. The receipt totals 120, 250 and 300 make a useful starting point because both the first rejected candidate and the two accepted candidates are visible. If the first evaluation changes a later record, write that dependency down next to the expression rather than relying on the field's final displayed value

Load and save the form from Delphi

The first Delphi routine opens a PDF containing the form and saves a copy. Before you add editing logic, start with a prepared document whose Template, dataset and scripts belong together; after the baseline round trip, add the field edits that the application actually performs

uses
  SysUtils, HPDFDoc, HPDFXFARuntime;

procedure SaveXfaSample(const InputFile, OutputFile: string);
var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile(InputFile) <= 0 then
      raise Exception.Create('Cannot open the PDF');
    Pdf.SaveLoadedDocument(OutputFile);
  finally
    Pdf.Free;
  end;
end;

The second routine reads XDP bytes directly and prints the values produced by the XFA widget runtime. JavaScript predicates need the script option enabled and a path to the shipped QuickJS bridge for the application's architecture. Supply page dimensions appropriate to the form you are loading as well. Merely loading a PDF does not determine the script policy of every host application, so exercise the configuration that will actually be deployed. The caller supplies the bridge path and owns the XDP data; the example frees the runtime after printing the widget values

procedure InspectBindingFixture(const XdpBytes: TBytes;
  const QuickJsPath: string);
var
  Options: TXFAWidgetRuntimeOptions;
  Runtime: TXFAWidgetRuntime;
  I: Integer;
begin
  Options := TXFAWidgetRuntimeOptions.Default;
  Options.ScriptOptions.EnableJavaScript := True;
  Options.ScriptOptions.QuickJSDLLPath := QuickJsPath;
  Runtime := TXFAWidgetRuntime.Create(XdpBytes, 595, 842, Options);
  try
    for I := 0 to Runtime.WidgetCount - 1 do
      Writeln('Widget ', I, ': ', Runtime.Widgets[I].Value);
  finally
    Runtime.Free;
  end;
end;

We ran the receipt through this runtime with the original data order and obtained 250. After exchanging the matching records, the same predicate returned 300. The QuickJS bridge path in an application must point to its deployed library and match the process architecture. Associate the printed value with the field name in the form being tested, because a widget's position can change when the layout changes. In our test the input totals were 120, 250 and 300; the only change between the two runs was the order of the matching totals

A test that explains the result

Keep the stages of a binding investigation distinct. Initial load evaluates the input data; a user event may subsequently edit that data; remerge asks the runtime to establish bindings again. A repeated group can expose both matches in order, whereas the scalar field reports one selected target. For a controlled data edit, read the field before and after the edit and again after an explicit remerge. If a predicate has side effects, log the candidate and the values it observes at each evaluation. We prefer a pure filter with updates in a named event wherever the form allows it, because a later change to record order then cannot quietly alter an update hidden inside the selection expression

If you get different totals from two datasets, examine their candidate order before changing the binding expression. The form may be applying the same first-match rule to different input. For surrounding application behavior, the dynamic XFA runtime guide covers interactive widgets, and XFA rich text and hyperlinks discusses another part of the form model. The Delphi routines above use the HotPDF Delphi Component