Akcja AcroForm to słownik dołączony do widżetu, który mówi przeglądarce, co zrobić, gdy z tym widżetem coś się stanie. Kliknij przycisk, a przeglądarka odczytuje jego słownik akcji: akcja URI otwiera adres internetowy, akcja JavaScript uruchamia skrypt, akcja SubmitForm wysyła zebrane wartości pól do punktu końcowego, akcja ResetForm czyści je z powrotem do wartości domyślnych. Akcja to dane, nie zachowanie zaszyte w pliku. ISO 32000-1 §12.6 definiuje kształt słownika; to przeglądarka dostarcza silnik, który go interpretuje. Ten podział ma znaczenie, bo akcja zapisana w PDF idealnie wciąż nic nie robi, jeśli czytnik po drugiej stronie nie ma dla niej silnika, a spora część udręki z AcroForm bierze się z tej luki, a nie z wadliwie zbudowanego pola
HotPDF zapisuje te słowniki bezpośrednio z Delphi i C++Builder, obok widżetów pól, do których się przyczepiają. Dla każdego formularza interaktywnego w grze są dwie struktury: widżet, który użytkownik widzi na stronie, oraz pole plus mechanika akcji pod spodem, niosąca dane i okablowanie. Są edytowane niezależnie, i każda z nich może być zła, podczas gdy druga wygląda dobrze. Poniższe sekcje przechodzą przez nazewnictwo pól, same akcje przycisków, JavaScript na poziomie pola oraz klasę defektu, która przetrwa kontrolę wizualną, bo mieszka całkowicie w tej drugiej strukturze
Nazwy pól to klucze routingu, nie podpisy
Każde pole AcroForm niesie w pełni kwalifikowaną nazwę. ISO 32000-1 §12.7.3 czyni tę nazwę, a nie widoczny podpis, kluczem, pod którym wartość pola podróżuje, gdy formularz jest eksportowany albo wysyłany. Deweloperzy przychodzący z projektowania VCL mają tendencję do traktowania nazwy kontrolki jak prywatnego identyfikatora w kodzie, a tutaj nią nie jest. To format transmisji
Pierwszą rzeczą, jaka z tego wynika, jest to, że dwa pola o tej samej w pełni kwalifikowanej nazwie nie są dwoma polami. PDF traktuje je jako dwie adnotacje widżetu jednego pola, dzielące jedną wartość, więc wpisywanie w jedno aktualizuje drugie natychmiast. To dokładnie to, czego chcesz, gdy nazwa klienta musi powtarzać się na każdej stronie umowy. To błąd, gdy pętla generująca przez przypadek ponownie użyje 'Field1' na trzech stronach. Żadna inspekcja wizualna nie złapie tego drugiego przypadku. Każda strona wciąż rysuje własne pole, a powiązanie ujawnia się dopiero, gdy ktoś zaczyna pisać
Nazwy z kropkami, takie jak applicant.email, budują hierarchię. Węzeł nadrzędny applicant grupuje swoje dzieci, co pozwala, by reset albo wysłanie celowały tylko w część formularza. Nazywanie pól w ten sposób od początku nic nie kosztuje, a zwraca się przy pierwszej okazji, gdy system odbierający poprosi tylko o blok applicant
Przyciski radiowe mają własną zasadę. Przyciski, które mają przełączać się razem, muszą dzielić nazwę grupy. W HotPDF wywołania AddRadioButton, które przekazują tę samą nazwę grupy, przyczepiają swoje widżety do jednego pola nadrzędnego, a wartość eksportu każdego przycisku ('basic' albo 'full') identyfikuje wybraną opcję. Nadaj każdemu przyciskowi odrębną nazwę, a dostaniesz rząd niezależnych przełączników wł/wył zamiast jednej wzajemnie wykluczającej się grupy, co renderuje się identycznie, a zachowuje błędnie
Tworzenie zestawu pól strona po stronie
HotPDF umieszcza pola przez metody THPDFPage, więc każde pole należy do obiektu strony, który je stworzył. Pułapką kolejności, na którą trzeba uważać, jest AddPage. Przekierowuje CurrentPage na nową stronę w chwili, gdy zwraca sterowanie, więc każde wywołanie pola po nim ląduje na nowej stronie, nawet gdy pole logicznie należało do strony, którą właśnie opuściłeś. Dokończ każdą stronę, narysowaną treść i pola razem, zanim wywołasz AddPage
procedure BuildClaimForm(Pdf: THotPDF);
begin
// Strona 1: blok applicant
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 wskazuje teraz na stronę 2
Pdf.CurrentPage.AddListBox('riders', 'None',
['None', 'Flood', 'Earthquake'], Rect(50, 500, 200, 600));
end;
Współrzędne używają konwencji PDF, z początkiem układu w lewym dolnym rogu strony. To ten sam początek, którego TextOut używa dla rysowanego tekstu, więc Rect(50, 100, 200, 120) siedzi blisko dołu strony Letter, nie u góry. VCL umieszcza Y na górze i zwiększa je w dół, więc tabela układu przeniesiona wprost wychodzi lustrzanie odbita w pionie, każde pole przerzucone na zły koniec strony. Zrób konwersję raz, we wspólnym helperze, zamiast w każdym miejscu wywołania, a jedna poprawka naprawi cały formularz
Podpinanie przycisków do akcji URI, JavaScript i wysyłania
Przycisk push jest bezwładny, dopóki nie dołączy się do niego akcji. HotPDF udostępnia typy akcji z ISO 32000-1 §12.6.4 przez wyliczenie THPDFButtonAction (baURI, baJavaScript, baSubmitURL, baResetForm, baHide, baShow, baNamed), i dostarcza dwie metody, które tworzą przycisk i wiążą jego akcję w jednym wywołaniu
// Otwórz stronę pomocy w systemowej przeglądarce
Pdf.CurrentPage.AddPushButtonWithAction('btnHelp', 'Help',
'https://www.example.com/claims-help', Rect(320, 700, 420, 730), baURI);
// Uruchom JavaScript po stronie przeglądarki
Pdf.CurrentPage.AddPushButtonWithAction('btnRecalc', 'Recalculate',
'app.alert("Totals updated.");', Rect(320, 660, 420, 690), baJavaScript);
// Wyślij jako XFDF i zachowaj puste pola w danych
Pdf.CurrentPage.AddPushButtonWithSubmitAction('btnSubmit', 'Submit claim',
'https://api.example.com/claims', Rect(320, 620, 420, 650),
[sffXFDF, sffIncludeNoValueFields]);
Flagi wysyłania zasługują na więcej namysłu, niż zwykle dostają. AddPushButtonWithSubmitAction przyjmuje zbiór THPDFSubmitFormFlags, a pusty zbiór produkuje zwykły post url-encoded, format, który wiele przykładowych punktów końcowych akceptuje, a wiele produkcyjnych odrzuca. Dodanie sffXFDF przełącza dane na XFDF. sffGetMethod zmienia czasownik HTTP. sffIncludeNoValueFields zachowuje puste pola w danych zamiast po cichu je porzucać, co ma znaczenie w chwili, gdy odbiorca odróżnia "nieobecne" od "puste". Zbiór flag jest częścią twojego kontraktu interfejsu z odbierającym punktem końcowym, więc uzgodnij go z zespołem, który parsuje wysłane dane, a nie po pierwszej odrzuconej partii
JavaScript na poziomie pola: keystroke, format, validate
Kliknięcia przycisków to nie jedyne miejsce, w którym żyją akcje. HotPDF dołącza też JavaScript do zdarzeń poszczególnych pól, które wyzwalają przeglądarki obsługujące skrypty, gdy użytkownik wprowadza dane. Są trzy wyzwalacze, i uruchamiają się w różnych momentach cyklu życia wejścia. Akcja keystroke uruchamia się przy nadejściu każdego znaku, i ponownie przy zatwierdzeniu. Akcja format przepisuje wyświetlaną wartość po zatwierdzeniu zmiany, czysto dla prezentacji. Akcja validate ma ostatnie słowo, akceptując albo odrzucając zatwierdzoną wartość, zanim stanie się wartością pola
// Odrzuć zatwierdzone wartości, które nie są prawdopodobnymi adresami e-mail
Pdf.AttachFieldKeyStrokeAction('applicant.email',
'if (event.willCommit && !/^[\w.-]+@[\w.-]+\.\w+$/.test(event.value)) event.rc = false;');
// Wyświetlaj amerykańskie numery telefonów jako (NNN) NNN-NNNN
Pdf.AttachFieldFormatAction('applicant.phone',
'event.value = event.value.replace(/(\d{3})(\d{3})(\d{4})/, "($1) $2-$3");');
// Odrzuć wnioskodawców poniżej 18 lat w chwili zatwierdzenia
Pdf.AttachFieldValidateAction('applicant.age',
'if (parseInt(event.value) < 18) event.rc = false;');
Ustawienie event.rc = false wewnątrz skryptu keystroke albo validate mówi przeglądarce, żeby odrzuciła wejście. Haczyk w tym, że nic z tego nie działa, jeśli przeglądarka nie ma silnika JavaScript. Acrobat i kilka produktów desktopowych go mają. Większość czytników mobilnych, silników renderujących wbudowanych w przeglądarkę i potoków druku nie ma, i po prostu porzucają skrypty bez słowa. Więc skrypty pola poprawiają jakość danych dla podzbioru użytkowników, których czytnik je uruchamia, i to wszystko, co robią. Nie są granicą bezpieczeństwa. Każda wysłana wartość wciąż musi zostać zwalidowana na serwerze, gdy dotrze, bo nie możesz zakładać, że klient cokolwiek sprawdził
Defekty, które przechodzą przegląd wizualny
Najtrudniejsze do złapania defekty AcroForm to te, które mieszkają w strukturze danych, a nie w renderowaniu, bo otwarcie pliku i spojrzenie na niego niczego nie mówi. Cztery pojawiają się na tyle często, że warto je nazwać, a każdy ma mechaniczny test, który znajduje go przed wydaniem
- Dryf wartości eksportu. Pole wyboru utworzone jako
AddCheckBox('consent', 'Yes', ...)wysyłaYes. Odbiorca, który dopasowuje poY, odrzuca każde zgłoszenie, podczas gdy strona wygląda idealnie. Wypełnij formularz, wyeksportuj go jako XFDF z Acrobata i zdiffuj wartości ze schematem, którego odbiorca faktycznie oczekuje - Przypadkowe lustrzenie wartości. Dwa pola dzielące w pełni kwalifikowaną nazwę zlewają się w jedno. Objaw ujawnia się w chwili wprowadzania danych, a nigdy w chwili generowania, więc testem jest wpisywanie w formularz, a nie jego wyrenderowanie i ocena wyniku wzrokiem
- Wartości combo spoza listy opcji. Gdy aktualna wartość przekazana do
AddComboBoxnie jest jedną z wymienionych opcji, przeglądarki nie zgadzają się co do tego, czy ją pokazać, wyczyścić, czy oflagować. Trzymaj wartość domyślną wewnątrz listy, a niezgoda znika - Pola wciąż edytowalne po zamknięciu procesu. HotPDF nie ma wywołania spłaszczającego wygląd dla pól AcroForm. Wspieranym sposobem zamrożenia ukończonego formularza jest tworzenie pól z flagą
ffReadOnly, która utrzymuje wartość widoczną poprzez własny strumień wyglądu pola, jednocześnie odmawiając edycji. Pole pozostaje żywym obiektem formularza, czego oczekują narzędzia do dalszego składania i podpisywania
Jedno zachowanie po stronie przeglądarki zasługuje na notatkę regresyjną, mimo że żadna zmiana kodu tego nie adresuje. Wdrożenia korporacyjnego Acrobata mogą wyłączać JavaScript albo ograniczać cele wysyłania przez politykę, więc akcja, która działała przez każdy build deweloperski, może leżeć martwa na zablokowanym pulpicie klienta. Zaplanuj widoczny fallback na wypadek, gdy przycisk nic nie robi, nawet jeśli tym fallbackiem jest tylko drukowana instrukcja mówiąca użytkownikowi, co zrobić zamiast tego
Gdzie praca nad formularzem łączy się z resztą dokumentu
Pole podpisu to samo w sobie typ pola AcroForm. Formularz, który zostanie później certyfikowany albo kontrasygnowany, lepiej zarezerwuje to pole podczas generowania, niż mieć je doklejone później, a przyczyny na poziomie bajtów są w towarzyszącym artykule o podpisach cyfrowych i podpisywaniu PAdES z HotPDF. Wejścia, które przychodzą jako pakiety XFA zamiast natywnego AcroForm, to inna sytuacja: spłaszczanie XFA do pól AcroForm to własny przepływ pracy z własnym modelem strat, bo obie technologie formularzy nie mogą współistnieć w jednym pliku
Metody pól, akcji i wyzwalaczy pokazane tutaj są częścią standardowego API HotPDF Delphi Component dla Delphi i C++Builder; strona produktu odsyła do pełnej dokumentacji, w tym przeciążeń flag pola i kompletnego wyliczenia flag wysyłania