Действието на AcroForm е речник, прикрепен към widget, който казва на зрителя какво да прави, когато нещо се случи с този widget. Щракнете върху бутон и визуализаторът прочита неговия речник за действие: URI действие отваря уеб адрес, JavaScript действие изпълнява скрипт, SubmitForm действие изпраща събраните стойности на полетата към крайна точка, а ResetForm действие ги връща към стойностите по подразбиране. Действието е данни, а не поведение, вградено във файла. ISO 32000-1 §12.6 определя формата на речника; зрителят предоставя двигателя, който го интерпретира. Това разделение има значение, защото дори перфектно записано действие в PDF не прави нищо, ако програмата за четене от другата страна няма двигател за него, а много проблеми с AcroForm се връщат точно към тази празнина, а не към неправилно оформено поле
HotPDF записва тези речници директно от Delphi и C++Builder, редом с widget-ите на полетата, към които са закачени. Всяка интерактивна форма има две структури: widget-ът, който потребителят вижда на страницата, и механизмът на полето и действието под него, който пренася данните и връзките. Те се редактират независимо и всяко едно от тях може да е грешно, докато другото изглежда наред. Следващите раздели разглеждат именуването на полетата, самите действия за бутоните, JavaScript на ниво поле и вида дефект, който минава визуална проверка, защото живее изцяло във втората структура
Имената на полетата са маршрутизиращи ключове, не надписи
Всяко поле на AcroForm носи напълно квалифицирано име. ISO 32000-1 §12.7.3 прави именно това име, а не видимия надпис, ключът, под който стойността на полето пътува при експортиране или изпращане на формуляра. Разработчиците, които идват от VCL дизайн, често третират името на контролата като частен кодов идентификатор, но тук не е такъв. То е wire format
Първото следствие е, че две полета със същото напълно квалифицирано име не са две полета. PDF ги третира като две widget анотации на едно поле, които споделят една стойност, така че въвеждането в едното обновява другото на място. Това е точно правилното поведение, когато името на клиента трябва да се повтори на всяка страница на договор. Това е бъг, когато генерационен цикъл използва 'Field1' на три страници по невнимание. Визуална проверка не хваща втория случай. Всяка страница все още рисува собствено поле и връзката се вижда чак когато някой започне да пише
Точкови имена като applicant.email изграждат йерархия. Родителският възел applicant групира децата си, което позволява reset или submit да таргетира само част от формуляра. Именуването на полетата по този начин от самото начало не струва нищо и се отплаща още първия път, когато приемащата система поиска само блока на кандидата
Радиобутоните имат собствено правило. Бутоните, които трябва да се превключват заедно, трябва да споделят име на група. В HotPDF извикванията на AddRadioButton, които подават едно и също име на група, прикачват widget-ите към едно родителско поле и експортираната стойност на всеки бутон ('basic' или 'full') идентифицира избраната опция. Дайте на всеки бутон различно име и ще получите редица независими ключове за включване и изключване вместо една взаимно изключваща се група, която се рендира еднакво, но се държи погрешно
Създаване на набора от полета страница по страница
HotPDF поставя полетата чрез методи на THPDFPage, така че всяко поле принадлежи на обекта на страницата, който го е създал. Клопката, за която трябва да внимавате, е AddPage. Той пренасочва CurrentPage към новата страница в момента, в който се върне, така че всяко извикване за поле след това попада на новата страница, дори когато логически е трябвало да принадлежи на страницата, която току-що сте оставили. Завършвайте всяка страница, заедно с рисуваното съдържание и полетата, преди да извикате AddPage
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;
Координатите използват PDF конвенцията, с начало в долния ляв ъгъл на страницата. Това е същият произход, който TextOut използва за рисуван текст, така че Rect(50, 100, 200, 120) стои близо до долната част на Letter страница, а не отгоре. VCL поставя Y в горната част и го увеличава надолу, така че таблица за оформление, пренесена буквално, излиза вертикално огледална, а всяко поле се озовава на грешния край на страницата. Направете преобразуването веднъж в общ helper вместо на всяко място за извикване и една корекция оправя цялата форма
Свързване на бутони към URI, JavaScript и действия за изпращане
Push бутонът е неактивен, докато към него не бъде прикрепено действие. HotPDF излага типовете действия от ISO 32000-1 §12.6.4 чрез изброяването THPDFButtonAction (baURI, baJavaScript, baSubmitURL, baResetForm, baHide, baShow, baNamed) и предоставя два метода, които създават бутона и свързват действието му с едно извикване
// 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]);
Флаговете за изпращане заслужават повече внимание, отколкото обикновено получават. AddPushButtonWithSubmitAction приема набор THPDFSubmitFormFlags, а празният набор създава обикновен url-encoded post, който много примерни крайни точки приемат, а много продукционни крайни точки отхвърлят. Добавянето на sffXFDF превключва payload-а към XFDF. sffGetMethod променя HTTP глагола. sffIncludeNoValueFields запазва празните полета в payload-а вместо да ги изпуска тихо, което има значение в момента, в който потребителят прави разлика между „липсва“ и „празно“. Наборът от флагове е част от интерфейсния договор с приемащата крайна точка, така че го уточнете с екипа, който обработва изпращането, а не след първата върната грешка
JavaScript на ниво поле: натискане, формат, проверка
Кликовете върху бутони не са единственото място, където живеят действията. HotPDF прикрепя и JavaScript към събитията на отделните полета, които скриптовите зрители задействат, докато потребителят въвежда данни. Има три тригера и те се задействат в различни моменти от жизнения цикъл на въвеждането. Действие при натискане на клавиш се изпълнява при всеки въведен знак и отново при потвърждаване. Действие за формат пренаписва показаната стойност след като промяната е приета, само за представяне. Действие за проверка има последната дума и приема или отхвърля потвърдената стойност, преди тя да стане стойност на полето
// 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;');
Задаването на event.rc = false в скрипт за натискане на клавиш или за проверка казва на зрителя да отхвърли въвеждането. Клопката е, че нищо от това не се изпълнява, освен ако зрителят не включва JavaScript двигател. Acrobat и няколко настолни продукта имат такъв. Повечето мобилни четци, вградени в браузър рендеръри и печатни канали нямат, и те пропускат скриптовете без предупреждение. Така че скриптовете на полетата подобряват качеството на данните само за подмножеството потребители, чиито четци ги изпълняват, и това е всичко, което правят. Те не са граница за сигурност. Всяка изпратена стойност все пак трябва да бъде проверена на сървъра, след като пристигне, защото не можете да предположите, че клиентът е проверил нещо
Дефекти, които минават визуален преглед
Най-трудните дефекти в AcroForm за откриване са онези, които живеят в структурата на данните, а не в рендирането, защото отварянето на файла и поглеждането към него не казва нищо. Четири се появяват достатъчно често, за да си струва да бъдат назовани, и всеки има механичен тест, който го хваща преди излизане
- Отклонение на стойността при експортиране. Квадратче за отметка, създадено като
AddCheckBox('consent', 'Yes', ...), изпращаYes. Потребител, който очакваY, ще отхвърли всяко изпращане, докато страницата изглежда перфектно. Попълнете формата, експортирайте го като XFDF от Acrobat и сравнете стойностите със схемата, която реално очаква потребителят - Случайно огледално дублиране на стойност. Две полета, които споделят напълно квалифицирано име, се сливат в едно. Симптомът се появява при въвеждането на данни и никога при генерирането, така че тестът е да въвеждате във формата, а не просто да я рендирате и оглеждате резултата
- Стойности на combo извън списъка с опции. Когато текущата стойност, подадена на
AddComboBox, не е една от изброените опции, различните зрители не са единодушни дали да я покажат, изчистят или маркират. Дръжте подразбиращата се стойност в списъка и разногласието изчезва - Полета, които остават редактиращи се след затваряне на работния поток. HotPDF няма извикване за flattening на appearance за полетата на AcroForm. Поддържаният начин да заключите завършена форма е да създадете полетата с флага
ffReadOnly, който запазва стойността видима през собствения stream за appearance на полето, но отказва редакции. Полето остава жив формулярен обект, какъвто надолу по веригата очакват инструментите за сглобяване и подписване
Едно поведение от страна на зрителя заслужава бележка за регресия, въпреки че нито една промяна в кода не го адресира. Корпоративните разгръщания на Acrobat могат да изключат JavaScript или да ограничат целите за изпращане по политика, така че действие, което е работило през всяка тестова компилация, може да стои безжизнено на заключен потребителски компютър. Планирайте видим резервен вариант за случая, в който бутонът не прави нищо, дори този резервен вариант да е само отпечатана инструкция, която казва на потребителя какво да направи вместо това
Къде работата с формуляри се свързва с останалата част от документа
Полето за подпис само по себе си е тип AcroForm поле. Формуляр, който по-късно ще бъде сертифициран или подписан повторно, е по-добре да резервира това поле по време на генерирането, вместо да го добавя впоследствие, а причините на ниво байтове са обяснени в съпътстващата статия за цифрови подписи и PAdES подписване с HotPDF. Входове, които пристигат като XFA пакети, а не като нативен AcroForm, са различен случай: flattening на XFA в полета на AcroForm е собствен работен поток със собствен модел на загуба, защото двете формулярни технологии не могат да съществуват в един и същ файл
Методите за поле, действие и тригер, показани тук, са част от стандартния HotPDF Component API за Delphi и C++Builder; продуктовата страница съдържа пълната документация, включително overload-ите за флаговете на полетата и пълното изброяване на submit флаговете