Action AcroForm adalah dictionary yang dilekatkan pada sebuah widget, memberi tahu viewer apa yang harus dilakukan ketika sesuatu terjadi pada widget tersebut. Klik sebuah tombol dan viewer membaca dictionary action-nya: action URI membuka alamat web, action JavaScript menjalankan script, action SubmitForm mengirim nilai field yang terkumpul ke sebuah endpoint, action ResetForm mengembalikannya ke nilai default. Action adalah data, bukan perilaku yang tertanam di dalam file. ISO 32000-1 §12.6 mendefinisikan bentuk dictionary tersebut; viewer yang menyediakan engine yang menerjemahkannya. Pemisahan ini penting karena sebuah action yang ditulis dengan sempurna ke dalam PDF tetap tidak melakukan apa-apa jika reader di sisi lain tidak memiliki engine untuk itu, dan banyak masalah AcroForm justru bersumber dari celah tersebut, bukan dari field yang cacat
HotPDF menulis dictionary tersebut langsung dari Delphi dan C++Builder, bersama widget field yang menjadi induknya. Ada dua struktur yang terlibat pada setiap form interaktif: widget yang dilihat pengguna di halaman, dan mesin field plus action di baliknya yang membawa data serta pengkabelannya. Keduanya diedit secara independen, dan salah satu bisa saja salah sementara yang lain tampak baik-baik saja. Bagian-bagian di bawah ini membahas penamaan field, action tombol itu sendiri, JavaScript tingkat field, serta jenis cacat yang lolos dari pemeriksaan visual karena sepenuhnya berada dalam struktur kedua
Nama field adalah kunci perutean, bukan caption
Setiap field AcroForm membawa sebuah nama yang fully qualified. ISO 32000-1 §12.7.3 menjadikan nama tersebut, bukan caption yang terlihat, sebagai kunci tempat nilai field itu berpindah saat form diekspor atau disubmit. Developer yang datang dari desain VCL cenderung memperlakukan nama sebuah control sebagai identifier kode privat, padahal di sini bukan begitu. Ini adalah format transmisi data
Konsekuensi pertamanya adalah dua field dengan nama fully qualified yang sama bukanlah dua field. PDF memperlakukan keduanya sebagai dua widget annotation dari satu field yang sama, berbagi satu nilai, sehingga mengetik di salah satu langsung memperbarui yang lain saat itu juga. Ini persis yang diinginkan ketika nama pelanggan harus berulang di setiap halaman sebuah kontrak. Namun ini menjadi bug ketika sebuah loop generasi tidak sengaja menggunakan ulang 'Field1' di tiga halaman. Tidak ada pemeriksaan visual yang menangkap kasus kedua ini. Setiap halaman tetap menggambar kotaknya sendiri, dan keterkaitan itu baru muncul begitu seseorang mulai mengetik
Nama bertitik seperti applicant.email membangun sebuah hierarki. Node induk applicant mengelompokkan anak-anaknya, dan inilah yang memungkinkan sebuah reset atau submit menyasar hanya sebagian dari form. Memberi nama field dengan cara ini sejak awal tidak memakan biaya apa pun, dan manfaatnya langsung terasa begitu sistem penerima meminta hanya blok applicant saja
Radio button memiliki aturannya sendiri. Tombol-tombol yang seharusnya beralih bersama harus berbagi satu nama group. Di HotPDF, pemanggilan AddRadioButton yang meneruskan nama group yang sama akan melekatkan widget-widgetnya pada satu field induk, dan nilai ekspor tiap tombol ('basic' atau 'full') mengidentifikasi opsi yang dipilih. Berikan nama berbeda pada setiap tombol dan hasilnya adalah sederet saklar on/off independen, bukan satu group yang saling eksklusif — tampilannya identik namun perilakunya keliru
Membuat kumpulan field halaman demi halaman
HotPDF menempatkan field melalui method THPDFPage, sehingga setiap field menjadi milik objek halaman yang membuatnya. Jebakan urutan yang perlu diwaspadai adalah AddPage. Method ini langsung mengarahkan ulang CurrentPage ke halaman baru begitu ia kembali, sehingga panggilan field apa pun setelahnya akan jatuh ke halaman baru, meskipun secara logis field itu milik halaman yang baru saja ditinggalkan. Selesaikan setiap halaman, konten yang digambar maupun field-nya sekaligus, sebelum memanggil AddPage
procedure BuildClaimForm(Pdf: THotPDF);
begin
// Halaman 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 sekarang menunjuk ke halaman 2
Pdf.CurrentPage.AddListBox('riders', 'None',
['None', 'Flood', 'Earthquake'], Rect(50, 500, 200, 600));
end;
Koordinat menggunakan konvensi PDF, dengan titik origin di sudut kiri bawah halaman. Ini origin yang sama dengan yang digunakan TextOut untuk teks yang digambar, sehingga Rect(50, 100, 200, 120) berada di dekat bagian bawah halaman Letter, bukan di bagian atas. VCL menempatkan Y di bagian atas dan bertambah ke bawah, sehingga sebuah tabel layout yang dipindahkan langsung apa adanya akan muncul terbalik secara vertikal, setiap field terlempar ke ujung halaman yang salah. Lakukan konversi ini sekali saja di sebuah helper bersama alih-alih di setiap titik pemanggilan, dan satu koreksi tunggal akan memperbaiki seluruh form
Menghubungkan tombol ke action URI, JavaScript, dan submit
Sebuah push button tidak berdaya sampai sebuah action dilekatkan padanya. HotPDF menampilkan jenis-jenis action dari ISO 32000-1 §12.6.4 melalui enumerasi THPDFButtonAction (baURI, baJavaScript, baSubmitURL, baResetForm, baHide, baShow, baNamed), dan menyediakan dua method yang membuat tombol sekaligus mengikat action-nya dalam satu pemanggilan
// Membuka halaman bantuan di browser sistem
Pdf.CurrentPage.AddPushButtonWithAction('btnHelp', 'Help',
'https://www.example.com/claims-help', Rect(320, 700, 420, 730), baURI);
// Menjalankan JavaScript sisi viewer
Pdf.CurrentPage.AddPushButtonWithAction('btnRecalc', 'Recalculate',
'app.alert("Totals updated.");', Rect(320, 660, 420, 690), baJavaScript);
// Submit sebagai XFDF dan pertahankan field kosong dalam payload
Pdf.CurrentPage.AddPushButtonWithSubmitAction('btnSubmit', 'Submit claim',
'https://api.example.com/claims', Rect(320, 620, 420, 650),
[sffXFDF, sffIncludeNoValueFields]);
Flag submit ini layak dipikirkan lebih dalam daripada yang biasanya dilakukan orang. AddPushButtonWithSubmitAction menerima sebuah set THPDFSubmitFormFlags, dan set kosong menghasilkan post url-encoded biasa, format yang diterima banyak endpoint contoh namun ditolak banyak endpoint produksi. Menambahkan sffXFDF mengubah payload menjadi XFDF. sffGetMethod mengubah verb HTTP yang dipakai. sffIncludeNoValueFields mempertahankan field kosong dalam payload alih-alih diam-diam membuangnya, yang menjadi penting begitu konsumen membedakan "tidak ada" dari "kosong". Kumpulan flag ini adalah bagian dari kontrak antarmuka dengan endpoint penerima, jadi sepakati bersama tim yang mem-parsing hasil submit tersebut, bukan setelah batch pertama ditolak
JavaScript tingkat field: keystroke, format, validate
Klik tombol bukan satu-satunya tempat action berada. HotPDF juga melekatkan JavaScript pada event per-field yang dipicu oleh viewer yang mendukung script saat pengguna sedang memasukkan data. Ada tiga trigger, dan masing-masing terpicu pada titik berbeda dalam siklus hidup input. Action keystroke berjalan setiap kali sebuah karakter masuk, dan sekali lagi saat commit. Action format menulis ulang nilai yang ditampilkan setelah sebuah perubahan di-commit, semata-mata untuk presentasi. Action validate mendapat kata akhir, menerima atau menolak nilai yang di-commit sebelum nilai itu menjadi nilai resmi field
// Menolak nilai yang di-commit bila bukan alamat email yang valid
Pdf.AttachFieldKeyStrokeAction('applicant.email',
'if (event.willCommit && !/^[\w.-]+@[\w.-]+\.\w+$/.test(event.value)) event.rc = false;');
// Menampilkan nomor telepon AS sebagai (NNN) NNN-NNNN
Pdf.AttachFieldFormatAction('applicant.phone',
'event.value = event.value.replace(/(\d{3})(\d{3})(\d{4})/, "($1) $2-$3");');
// Menolak pemohon di bawah 18 tahun saat commit
Pdf.AttachFieldValidateAction('applicant.age',
'if (parseInt(event.value) < 18) event.rc = false;');
Mengatur event.rc = false di dalam script keystroke atau validate memberi tahu viewer untuk menolak input tersebut. Yang jadi masalah, semua ini hanya berjalan jika viewer menyertakan JavaScript engine. Acrobat dan beberapa produk desktop memilikinya. Sebagian besar reader mobile, renderer yang tertanam di browser, dan pipeline cetak tidak memilikinya, dan mereka membuang script itu begitu saja tanpa keluhan. Jadi script field hanya meningkatkan kualitas data untuk sebagian pengguna yang reader-nya menjalankan script tersebut, dan itu saja batasnya. Script ini bukan batas keamanan. Setiap nilai yang disubmit tetap harus divalidasi di server begitu tiba, karena tidak bisa diasumsikan bahwa client sudah memeriksa apa pun
Cacat yang lolos dari review visual
Cacat AcroForm yang paling sulit ditangkap adalah yang berada dalam struktur data, bukan dalam rendering, karena membuka file dan melihatnya tidak memberi tahu apa-apa. Ada empat yang cukup sering muncul sehingga layak disebutkan, dan masing-masing punya uji mekanis yang bisa menemukannya sebelum rilis
- Pergeseran nilai ekspor. Sebuah checkbox yang dibuat dengan
AddCheckBox('consent', 'Yes', ...)mengirimYes. Konsumen yang mencocokkan denganYakan menolak setiap submission padahal halamannya tampak sempurna. Isi form, ekspor sebagai XFDF dari Acrobat, lalu diff nilai-nilainya terhadap skema yang benar-benar diharapkan konsumen - Pencerminan nilai yang tidak disengaja. Dua field yang berbagi nama fully qualified yang sama akan menyatu menjadi satu. Gejalanya muncul saat data dimasukkan, bukan saat form dibuat, sehingga ujinya adalah mengetik ke dalam form, bukan me-render lalu melihat hasilnya secara sekilas
- Nilai combo di luar daftar opsi. Ketika nilai saat ini yang diteruskan ke
AddComboBoxbukan salah satu opsi yang terdaftar, viewer akan berbeda pendapat soal apakah harus menampilkannya, mengosongkannya, atau menandainya. Pastikan default tetap berada dalam daftar dan perbedaan pendapat itu pun hilang - Field yang masih bisa diedit setelah workflow ditutup. HotPDF tidak memiliki pemanggilan appearance-flattening untuk field AcroForm. Cara yang didukung untuk membekukan form yang sudah selesai adalah membuat field dengan flag
ffReadOnly, yang menjaga nilai tetap terlihat melalui appearance stream milik field itu sendiri sambil menolak pengeditan. Field tersebut tetap menjadi objek form yang hidup, yang memang diharapkan ditemukan oleh tool assembly dan signing di hilir
Ada satu perilaku sisi viewer yang layak dicatat sebagai catatan regresi meskipun tidak ada perubahan kode yang mengatasinya. Deployment Acrobat enterprise dapat menonaktifkan JavaScript atau membatasi target submit lewat kebijakan, sehingga sebuah action yang berfungsi baik di setiap build pengembangan bisa saja diam tak berfungsi di desktop pelanggan yang dikunci ketat. Rencanakan sebuah fallback yang terlihat untuk kasus ketika tombol tidak melakukan apa-apa, meskipun fallback itu hanya berupa instruksi tercetak yang memberi tahu pengguna apa yang harus dilakukan sebagai gantinya
Di mana pekerjaan form terhubung dengan bagian dokumen lainnya
Sebuah signature field pada dasarnya adalah salah satu jenis field AcroForm. Form yang nantinya akan disertifikasi atau ditandatangani ulang lebih baik mencadangkan field tersebut sejak saat generasi ketimbang menambalnya belakangan, dan alasan pada level byte untuk itu ada di artikel pendamping tentang digital signature dan PAdES signing dengan HotPDF. Input yang datang sebagai paket XFA alih-alih AcroForm native adalah situasi yang berbeda: meratakan (flattening) XFA menjadi field AcroForm adalah workflow tersendiri dengan model kehilangannya sendiri, karena dua teknologi form ini tidak bisa berdampingan dalam satu file
Method field, action, dan trigger yang ditunjukkan di sini merupakan bagian dari API standar HotPDF Delphi Component untuk Delphi dan C++Builder; halaman produk menautkan referensi lengkapnya, termasuk overload field-flag dan enumerasi submit-flag secara menyeluruh