Tehnički članak

Kreiranje AcroForm polja i akcija sa HotPDF-om u Delphi-ju

AcroForm akcija je rečnik zakačen za vidžet (widget) koji govori pregledaču šta da radi kada se nešto desi sa tim vidžetom. Kliknite na dugme i pregledač čita njegov rečnik akcija: URI akcija otvara veb adresu, JavaScript akcija pokreće skriptu, SubmitForm akcija šalje prikupljene vrednosti polja na krajnju tačku (endpoint), a ResetForm akcija ih vraća na podrazumevane vrednosti. Akcija je podatak, a ne ponašanje ugrađeno u fajl. ISO 32000-1 §12.6 definiše oblik rečnika; pregledač obezbeđuje mašinu koja ga interpretira. Ta podela je važna jer akcija koja je savršeno upisana u PDF i dalje ne radi ništa ako čitač na drugoj strani nema mašinu za nju, pa se mnogi problemi sa AcroForm-om svode na taj nedostatak pre nego na loše formirano polje

HotPDF upisuje ove rečnike direktno iz Delphi-ja i C++Builder-a, zajedno sa vidžetima polja na koje se odnose. Dve strukture su u igri za svaki interaktivni obrazac: vidžet koji korisnik vidi na stranici i mehanizam polja i akcije ispod njega koji nosi podatke i povezivanje. Oni se menjaju nezavisno, i jedno može biti pogrešno dok drugo izgleda sasvim u redu. Odeljci u nastavku bave se imenovanjem polja, samim akcijama dugmadi, JavaScript-om na nivou polja i klasom grešaka koje prolaze vizuelnu proveru jer žive isključivo u toj drugoj strukturi

Nazivi polja su ključevi za rutiranje, a ne natpisi

Svako AcroForm polje nosi potpuno kvalifikovano ime (fully qualified name). Standard ISO 32000-1 §12.7.3 definiše da je to ime, a ne vidljivi natpis (caption), ključ pod kojim vrednost polja putuje kada se obrazac eksportuje ili šalje. Programeri koji dolaze iz VCL okruženja skloni su da tretiraju ime kontrole kao privatni identifikator koda, ali to ovde nije slučaj. To je format prenosa (wire format)

Prva stvar koja iz toga proizlazi jeste da dva polja sa istim potpuno kvalifikovanim imenom nisu dva različita polja. PDF ih tretira kao dve vidžet anotacije jednog polja koje dele istu vrednost, tako da kucanje u jednom polju odmah ažurira drugo. To je upravo ono što želite kada ime kupca mora da se ponavlja na svakoj stranici ugovora. Međutim, to je greška kada petlja za generisanje slučajno ponovo koristi 'Field1' na tri stranice. Nikakva vizuelna inspekcija ne može uočiti ovaj drugi slučaj. Svaka stranica i dalje crta sopstveni okvir, a povezanost se javlja tek kada neko počne da kuca

Imena sa tačkom, kao što je applicant.email, grade hijerarhiju. Roditeljski čvor applicant grupiše svoju decu, što omogućava da resetovanje ili slanje cilja samo deo obrasca. Imenovanje polja na ovaj način od samog početka ne košta ništa, a isplati se čim sistem koji prima podatke zatraži samo blok za podnosioca prijave (applicant)

Radio dugmad imaju svoje pravilo. Dugmad koja treba da se menjaju zajedno moraju da dele ime grupe. U HotPDF-u, pozivi AddRadioButton koji prosleđuju isto ime grupe povezuju svoje vidžete sa jednim roditeljskim poljem, a izvozna vrednost svakog dugmeta ('basic' ili 'full') identifikuje izabranu opciju. Ako svakom dugmetu date različito ime, dobićete niz nezavisnih prekidača umesto jedne međusobno isključive grupe, što se vizuelno prikazuje isto ali radi pogrešno

Kreiranje skupa polja stranicu po stranicu

HotPDF postavlja polja preko metoda klase THPDFPage, tako da svako polje pripada objektu stranice koji ga je kreirao. Zamka u redosledu na koju treba paziti je AddPage. On usmerava CurrentPage na novu stranicu u trenutku kada se izvrši, tako da bilo koji poziv polja nakon toga završava na novoj stranici, čak i kada je polje logički pripadalo stranici koju ste upravo napustili. Završite svaku stranicu, iscrtani sadržaj i polja zajedno, pre nego što pozovete 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;

Koordinate koriste PDF konvenciju, sa početkom u donjem levom uglu stranice. Ovo je isti početak koji TextOut koristi za iscrtani tekst, tako da se Rect(50, 100, 200, 120) nalazi blizu dna Letter stranice, a ne na vrhu. VCL postavlja Y na vrh i povećava ga naniže, tako da tabela rasporeda prenesena direktno iz VCL-a ispada vertikalno ogledana, pri čemu je svako polje preokrenuto na pogrešnu stranu stranice. Uradite konverziju jednom u zajedničkoj pomoćnoj funkciji umesto na svakom mestu poziva, i jedna ispravka će rešiti ceo obrazac

Povezivanje dugmadi sa URI, JavaScript i akcijama slanja

Obično dugme (push button) je pasivno dok mu se ne dodeli akcija. HotPDF izlaže tipove akcija iz ISO 32000-1 §12.6.4 kroz nabrajanje THPDFButtonAction (baURI, baJavaScript, baSubmitURL, baResetForm, baHide, baShow, baNamed) i pruža dve metode koje kreiraju dugme i povezuju njegovu akciju u jednom pozivu

// 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]);

Zastavice za slanje (submit flags) zaslužuju više pažnje nego što im se obično pridaje. AddPushButtonWithSubmitAction prima skup THPDFSubmitFormFlags, pri čemu prazan skup proizvodi običan URL-kodiran POST zahtev, što je format koji mnoge testne krajnje tačke prihvataju, ali mnoge produkcione odbijaju. Dodavanje sffXFDF menja format podataka u XFDF. sffGetMethod menja HTTP metodu. sffIncludeNoValueFields zadržava prazna polja u podacima umesto da ih tiho odbaci, što je važno onog trenutka kada primalac razlikuje „odsutno“ od „praznog“. Skup zastavica je deo vašeg ugovora o interfejsu sa krajnjom tačkom koja prima podatke, pa to definišite sa timom koji analizira podatke o slanju, a ne tek nakon prve odbijene serije

JavaScript na nivou polja: pritisak na taster, format, validacija

Klikovi na dugmad nisu jedino mesto gde žive akcije. HotPDF takođe dodeljuje JavaScript događajima na nivou polja koje pokreću čitači sa podrškom za skripte dok korisnik unosi podatke. Postoje tri okidača (triggers) i oni se aktiviraju u različitim trenucima životnog ciklusa unosa. Keystroke akcija se pokreće kako svaki karakter stigne, i ponovo pri potvrdi unosa (commit). Format akcija menja prikazanu vrednost nakon što je promena potvrđena, isključivo radi prezentacije. Validate akcija ima poslednju reč, prihvatajući ili odbijajući potvrđenu vrednost pre nego što ona postane stvarna vrednost polja

// 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;');

Postavljanje event.rc = false unutar keystroke ili validate skripte govori čitaču da odbije unos. Kvaka je u tome što se ništa od ovoga ne izvršava osim ako čitač ne sadrži JavaScript mašinu. Acrobat i nekoliko desktop proizvoda je imaju. Većina mobilnih čitača, integrisanih renderera u pretraživačima i procesa štampanja nemaju, pa tiho ignorišu skripte bez ikakvog upozorenja. Dakle, skripte za polja poboljšavaju kvalitet podataka samo za onaj deo korisnika čiji čitač ih pokreće, i to je sve što rade. One nisu bezbednosna granica. Svaka poslata vrednost i dalje mora biti validirana na serveru kada stigne, jer ne možete pretpostaviti da je klijent bilo šta proverio

Greške koje prolaze vizuelni pregled

Najteže greške AcroForm-a za uočavanje su one koje žive u strukturi podataka, a ne u renderovanju, jer vam otvaranje i gledanje fajla ne govori ništa. Četiri greške se javljaju dovoljno često da ih vredi pomenuti, a svaka ima mehanički test koji je pronalazi pre objavljivanja

  • Odstupanje vrednosti za izvoz (export value). Polje za potvrdu kreirano kao AddCheckBox('consent', 'Yes', ...) šalje vrednost Yes. Primalac koji očekuje Y odbiće svako slanje iako stranica izgleda savršeno. Popunite obrazac, eksportujte ga kao XFDF iz Acrobat-a i uporedite vrednosti sa šemom koju primalac stvarno očekuje
  • Slučajno dupliranje vrednosti. Dva polja koja dele potpuno kvalifikovano ime spajaju se u jedno. Simptom se pojavljuje u trenutku unosa podataka, a nikada prilikom generisanja, tako da je test da kucate u obrazac, a ne da ga renderujete i vizuelno proveravate rezultat
  • Vrednosti kombinovanog okvira (combo values) van liste opcija. Kada trenutna vrednost prosleđena AddComboBox-u nije jedna od navedenih opcija, čitači se ne slažu oko toga da li da je prikažu, ostave praznu ili označe. Držite podrazumevanu vrednost unutar liste i nesuglasice nestaju
  • Polja koja su i dalje podložna izmenama nakon završetka toka posla. HotPDF nema poziv za poravnanje izgleda (appearance flattening) za AcroForm polja. Podržani način za zamrzavanje popunjenog obrasca je kreiranje polja sa zastavicom ffReadOnly, što održava vrednost vidljivom kroz sopstveni tok izgleda (appearance stream) polja dok istovremeno odbija izmene. Polje ostaje aktivni objekat obrasca, što je upravo ono što alati za sklapanje i potpisivanje nizvodno očekuju da nađu

Jedno ponašanje na strani čitača vredi zabeležiti iako ga nikakva promena koda ne rešava. Korporativne instalacije Acrobat-a mogu onemogućiti JavaScript ili ograničiti ciljeve slanja kroz bezbednosne polise, tako da akcija koja je radila kroz svaku razvojnu verziju može ostati neaktivna na zaključanom korisničkom računaru. Planirajte vidljivo alternativno rešenje (fallback) za slučaj kada dugme ne radi ništa, čak i ako je to samo odštampano uputstvo koje govori korisniku šta da radi umesto toga

Gde se rad sa obrascima povezuje sa ostatkom dokumenta

Polje za potpis je takođe tip AcroForm polja. Za obrazac koji će kasnije biti sertifikovan ili kosigniran, bolje je rezervisati to polje tokom generisanja nego ga naknadno krpiti, a razlozi na nivou bajtova nalaze se u pratećem članku o digitalnim potpisima i PAdES potpisivanju sa HotPDF-om. Unusi koji stižu kao XFA paketi umesto kao izvorni AcroForm su drugačija situacija: poravnanje XFA u AcroForm polja je poseban tok posla sa sopstvenim modelom gubitka podataka, jer dve tehnologije obrazaca ne mogu uporedo postojati u istom fajlu

Metode za polja, akcije i okidače prikazane ovde deo su standardnog HotPDF Component API-ja za Delphi i C++Builder; stranica proizvoda vodi do kompletne reference, uključujući preopterećenja za zastavice polja i kompletno nabrajanje zastavica za slanje