Eine AcroForm-Aktion ist ein Dictionary, das an ein Widget angehängt ist und dem Betrachter mitteilt, was bei einem Ereignis an diesem Widget geschehen soll. Ein Klick auf eine Schaltfläche veranlasst den Betrachter, das Aktions-Dictionary zu lesen: eine URI-Aktion öffnet eine Webadresse, eine JavaScript-Aktion führt ein Skript aus, eine SubmitForm-Aktion sendet die gesammelten Feldwerte an einen Endpunkt, eine ResetForm-Aktion setzt sie auf die Standardwerte zurück. Die Aktion ist Daten, kein in die Datei gebranntes Verhalten. ISO 32000-1 §12.6 definiert die Form des Dictionarys; der Betrachter stellt die Engine bereit, die es interpretiert. Diese Trennung ist wichtig, weil eine perfekt in die PDF geschriebene Aktion trotzdem nichts tut, wenn der Betrachter am anderen Ende keine Engine dafür hat, und viele AcroForm-Probleme lassen sich auf diese Lücke zurückführen statt auf ein fehlerhaftes Feld.
HotPDF schreibt diese Dictionarys direkt aus Delphi und C++Builder, zusammen mit den Feld-Widgets, an denen sie hängen. Für jedes interaktive Formular sind zwei Strukturen im Spiel: das Widget, das der Benutzer auf der Seite sieht, und das darunter liegende Feld samt Aktionsmechanismus, der die Daten und die Verdrahtung trägt. Beide werden unabhängig voneinander bearbeitet, und jedes kann falsch sein, während das andere korrekt wirkt. Die folgenden Abschnitte behandeln Feldnamen, die Schaltflächenaktionen selbst, feldbezogenes JavaScript und die Klasse von Fehlern, die eine Sichtprüfung übersteht, weil sie vollständig in der zweiten Struktur lebt.
Feldnamen sind Routing-Schlüssel, keine Beschriftungen
Jedes AcroForm-Feld trägt einen vollqualifizierten Namen. ISO 32000-1 §12.7.3 macht diesen Namen, nicht die sichtbare Beschriftung, zum Schlüssel, unter dem der Wert des Feldes beim Export oder Übermitteln des Formulars übertragen wird. Entwickler, die von der VCL-Gestaltung kommen, behandeln den Namen eines Steuerelements als privaten Code-Bezeichner, und das ist er hier nicht. Er ist das Drahtformat.
Daraus folgt als Erstes, dass zwei Felder mit demselben vollqualifizierten Namen keine zwei Felder sind. PDF behandelt sie als zwei Widget-Anmerkungen eines einzigen Feldes, die einen Wert teilen, sodass die Eingabe in eines das andere sofort aktualisiert. Das ist genau das, was gewünscht wird, wenn ein Kundenname auf jeder Seite eines Vertrags erscheinen muss. Es ist ein Fehler, wenn eine Generierungsschleife versehentlich 'Field1' über drei Seiten hinweg wiederverwendet. Keine Sichtprüfung erkennt den zweiten Fall. Jede Seite zeichnet noch ihre eigene Box, und die Verknüpfung taucht erst auf, wenn jemand zu tippen beginnt.
Namen mit Punkttrennung wie applicant.email bilden eine Hierarchie. Der Elternknoten applicant gruppiert seine Kinder, was es ermöglicht, dass ein Reset oder Submit nur einen Teil eines Formulars anspricht. Felder von Anfang an so zu benennen, kostet nichts und zahlt sich aus, sobald das empfangende System nur den Antragstellerblock anfordert.
Radiobuttons haben eine eigene Regel. Buttons, die zusammen umschalten sollen, müssen einen gemeinsamen Gruppennamen teilen. In HotPDF hängen AddRadioButton-Aufrufe, die denselben Gruppennamen übergeben, ihre Widgets an ein gemeinsames Elternfeld, und der Exportwert jedes Buttons ('basic' oder 'full') identifiziert die gewählte Option. Gibt man jedem Button einen eigenen Namen, erhält man eine Reihe unabhängiger An/Aus-Schalter statt einer echten Gruppe; das sieht identisch aus und verhält sich falsch.
Felder Seite für Seite anlegen
HotPDF platziert Felder über THPDFPage-Methoden, daher gehört jedes Feld zum Seitenobjekt, das es erstellt hat. Die Sequenzierungsfalle bei AddPage ist zu beachten: Es setzt CurrentPage sofort nach seiner Rückkehr auf die neue Seite um, sodass jeder Feldaufruf danach auf der neuen Seite landet, auch wenn das Feld logisch zur gerade verlassenen Seite gehörte. Jede Seite, gezeichneter Inhalt und Felder gemeinsam, sollte vollständig sein, bevor AddPage aufgerufen wird.
procedure BuildClaimForm(Pdf: THotPDF);
begin
// Page 1: applicant block
Pdf.CurrentPage.AddTextField('applicant.name', '', Rect(50, 700, 300, 722));
Pdf.CurrentPage.AddTextField('applicant.email', '', Rect(50, 660, 300, 682));
Pdf.CurrentPage.AddCheckBox('consent', 'Y', Rect(50, 620, 70, 640), False);
Pdf.CurrentPage.AddRadioButton('coverage', 'basic', Rect(50, 580, 70, 600), True);
Pdf.CurrentPage.AddRadioButton('coverage', 'full', Rect(90, 580, 110, 600), False);
Pdf.CurrentPage.AddComboBox('plan', 'Standard',
['Basic', 'Standard', 'Premium'], Rect(50, 540, 200, 565));
Pdf.AddPage; // CurrentPage now points at page 2
Pdf.CurrentPage.AddListBox('riders', 'None',
['None', 'Flood', 'Earthquake'], Rect(50, 500, 200, 600));
end;
Die Koordinaten verwenden die PDF-Konvention, mit dem Ursprung in der unteren linken Ecke der Seite. Das ist derselbe Ursprung, den TextOut für gezeichneten Text verwendet, sodass Rect(50, 100, 200, 120) nahe am unteren Rand einer Letter-Seite sitzt, nicht oben. VCL setzt Y oben an und lässt es nach unten wachsen, sodass eine direkt übertragene Layout-Tabelle vertikal gespiegelt erscheint, jedes Feld am falschen Ende der Seite. Die Konvertierung einmal in einem gemeinsamen Hilfsmittel durchführen, und eine einzige Korrektur behebt das gesamte Formular.
Schaltflächen mit URI-, JavaScript- und Submit-Aktionen verknüpfen
Eine Schaltfläche ist inaktiv, bis eine Aktion angehängt wird. HotPDF stellt die Aktionstypen aus ISO 32000-1 §12.6.4 über die THPDFButtonAction-Aufzählung bereit (baURI, baJavaScript, baSubmitURL, baResetForm, baHide, baShow, baNamed) und bietet zwei Methoden, die die Schaltfläche erstellen und ihre Aktion in einem Aufruf binden.
// Open a help page in the system browser
Pdf.CurrentPage.AddPushButtonWithAction('btnHelp', 'Help',
'https://www.example.com/claims-help', Rect(320, 700, 420, 730), baURI);
// Run viewer-side JavaScript
Pdf.CurrentPage.AddPushButtonWithAction('btnRecalc', 'Recalculate',
'app.alert("Totals updated.");', Rect(320, 660, 420, 690), baJavaScript);
// Submit as XFDF and keep empty fields in the payload
Pdf.CurrentPage.AddPushButtonWithSubmitAction('btnSubmit', 'Submit claim',
'https://api.example.com/claims', Rect(320, 620, 420, 650),
[sffXFDF, sffIncludeNoValueFields]);
Die Submit-Flags verdienen mehr Aufmerksamkeit als sie üblicherweise erhalten. AddPushButtonWithSubmitAction nimmt ein THPDFSubmitFormFlags-Set entgegen, und ein leeres Set erzeugt einen einfachen URL-kodierten POST, das Format, das viele Beispielendpunkte akzeptieren und viele Produktionsendpunkte ablehnen. Das Hinzufügen von sffXFDF wechselt die Nutzlast auf XFDF. sffGetMethod ändert das HTTP-Verb. sffIncludeNoValueFields behält leere Felder in der Nutzlast, statt sie stillschweigend zu verwerfen, was entscheidend ist, sobald der Verbraucher zwischen „nicht vorhanden" und „leer" unterscheidet. Das Flag-Set ist Teil des Schnittstellenvertrags mit dem empfangenden Endpunkt und sollte mit dem Team abgestimmt werden, das die Einreichung verarbeitet, nicht erst nach dem ersten abgelehnten Stapel.
Feldbezogenes JavaScript: Tastenanschlag, Format, Validierung
Schaltflächenklicks sind nicht der einzige Ort, an dem Aktionen leben. HotPDF hängt JavaScript auch an die feldspezifischen Ereignisse, die skriptfähige Betrachter während der Dateneingabe auslösen. Es gibt drei Auslöser, und sie feuern zu unterschiedlichen Zeitpunkten im Eingabe-Lebenszyklus. Eine Tastenanschlag-Aktion läuft bei jedem eintreffenden Zeichen und erneut beim Übernehmen. Eine Format-Aktion schreibt den angezeigten Wert nach einer übernommenen Änderung um, rein für die Darstellung. Eine Validierungsaktion hat das letzte Wort und akzeptiert oder verwirft den übernommenen Wert, bevor er zum Feldwert wird.
// Reject committed values that are not plausible email addresses
Pdf.AttachFieldKeyStrokeAction('applicant.email',
'if (event.willCommit && !/^[\w.-]+@[\w.-]+\.\w+$/.test(event.value)) event.rc = false;');
// Display US phone numbers as (NNN) NNN-NNNN
Pdf.AttachFieldFormatAction('applicant.phone',
'event.value = event.value.replace(/(\d{3})(\d{3})(\d{4})/, "($1) $2-$3");');
// Refuse applicants under 18 at commit time
Pdf.AttachFieldValidateAction('applicant.age',
'if (parseInt(event.value) < 18) event.rc = false;');
Das Setzen von event.rc = false in einem Tastenanschlag- oder Validierungsskript weist den Betrachter an, die Eingabe abzulehnen. Der Haken ist, dass nichts davon läuft, sofern der Betrachter keine JavaScript-Engine mitbringt. Acrobat und einige Desktop-Produkte haben eine. Die meisten mobilen Betrachter, in Browser eingebettete Renderer und Druckpipelines haben keine und verwerfen die Skripte stillschweigend. Feldbezogene Skripte verbessern also die Datenqualität für die Teilmenge von Benutzern, deren Betrachter sie ausführt, und das ist alles, was sie tun. Sie sind keine Sicherheitsgrenze. Jeder übermittelte Wert muss nach dem Eintreffen trotzdem serverseitig validiert werden, da nicht davon ausgegangen werden kann, dass der Client irgendetwas geprüft hat.
Fehler, die eine Sichtprüfung bestehen
Die schwierigsten AcroForm-Fehler zu erkennen sind die, die in der Datenstruktur statt im Rendering leben, denn das Öffnen der Datei und das Betrachten sagt nichts. Vier treten häufig genug auf, um benannt zu werden, und jeder hat einen mechanischen Test, der ihn vor der Veröffentlichung aufdeckt.
- Exportwert-Abweichung. Eine mit
AddCheckBox('consent', 'Yes', ...)erstellte Checkbox sendetYes. Ein Verbraucher, der aufYabgleicht, lehnt jede Einreichung ab, während die Seite einwandfrei aussieht. Das Formular ausfüllen, als XFDF aus Acrobat exportieren und die Werte gegen das Schema des Verbrauchers abgleichen. - Versehentliche Wertspiegelung. Zwei Felder, die denselben vollqualifizierten Namen teilen, verschmelzen zu einem. Das Symptom taucht zur Dateneingabezeit auf und nie zur Generierungszeit, daher ist der Test, das Formular auszufüllen, nicht es zu rendern und das Ergebnis zu betrachten.
- Combo-Werte außerhalb der Optionsliste. Wenn der an
AddComboBoxübergebene aktuelle Wert nicht in der Optionsliste enthalten ist, sind sich Betrachter uneinig, ob er angezeigt, ausgeblendet oder als Fehler markiert werden soll. Den Standardwert innerhalb der Liste halten und die Uneinigkeit verschwindet. - Felder noch bearbeitbar nach Abschluss des Workflows. HotPDF hat keinen Aufruf zum Einebnen des Erscheinungsbilds von AcroForm-Feldern. Der unterstützte Weg, ein ausgefülltes Formular einzufrieren, besteht darin, die Felder mit dem
ffReadOnly-Flag zu erstellen, wodurch der Wert über den eigenen Erscheinungsstrom des Feldes sichtbar bleibt, während Bearbeitungen abgelehnt werden. Das Feld bleibt ein aktives Formularobjekt, was nachgelagerte Montage- und Signiertools erwarten.
Ein betrachterseitiges Verhalten ist eine Regressionsnotiz wert, auch wenn keine Codeänderung es anspricht. Enterprise-Acrobat-Deployments können JavaScript deaktivieren oder Submit-Ziele per Richtlinie einschränken, sodass eine Aktion, die durch jeden Entwicklungs-Build funktioniert hat, auf einem gesperrten Kundenrechner tot sitzt. Einen sichtbaren Fallback für den Fall einplanen, dass die Schaltfläche nichts tut, auch wenn dieser Fallback nur eine gedruckte Anleitung ist, die dem Benutzer mitteilt, was er stattdessen tun soll.
Verbindung der Formulararbeit zum Rest des Dokuments
Ein Signaturfeld ist selbst ein AcroForm-Feldtyp. Ein Formular, das später zertifiziert oder gegengezeichnet wird, reserviert dieses Feld besser während der Generierung, als es danach einzufügen, und die Gründe dafür auf Byte-Ebene sind im Begleitartikel über digitale Signaturen und PAdES-Signierung mit HotPDF beschrieben. Eingaben, die als XFA-Pakete statt als natives AcroForm ankommen, sind eine andere Situation: das Einebnen von XFA in AcroForm-Felder ist ein eigener Workflow mit einem eigenen Verlustmodell, da beide Formulartechnologien nicht in einer Datei koexistieren können.
Die hier gezeigten Feld-, Aktions- und Auslösermethoden sind Teil der Standard-API der HotPDF Component für Delphi und C++Builder; die Produktseite verlinkt die vollständige Referenz, einschließlich der Feld-Flag-Überladungen und der vollständigen Submit-Flag-Aufzählung.