Sebuah field formulir PDF sendiri hanyalah sebuah kotak yang menyimpan nilai. Apa yang membuat formulir berperilaku seperti aplikasi kecil adalah tindakan yang dilampirkan padanya: klik yang menyembunyikan bagian, menarik kembali nilai yang disimpan dari file, melompat ke halaman terakhir, atau menjalankan skrip yang menjumlahkan kolom. Semua itu tidak berada di dalam field. Tindakan tersebut berada dalam kamus tindakan (action dictionary), dan ISO 32000-1 mengatur seluruh keluarga ini di §12.6. Artikel ini membahas tindakan yang paling sering digunakan oleh program Delphi dan menunjukkan bagaimana PDFlibPas menghubungkan masing-masing tindakan ke field atau tautan.
Model mental yang perlu diingat adalah bahwa field dan tindakan merupakan objek terpisah yang digabungkan oleh sebuah referensi. Anotasi widget atau anotasi tautan membawa tindakan di entri /A miliknya. Tindakan tersebut menamai field yang dioperasikannya berdasarkan judul, bukan berdasarkan indeks, sehingga judul yang Anda berikan pada field adalah handle yang digunakan oleh setiap tindakan selanjutnya untuk menemukannya. Setelah pemisahan itu jelas, API tidak lagi terlihat seperti kumpulan panggilan acak dan mulai terlihat seperti satu pola yang diterapkan pada empat jenis kata kerja.
Named actions: navigasi tanpa nomor halaman
Tindakan paling sederhana tidak membawa parameter sama sekali. ISO 32000-1 §12.6.4.11, Tabel 194, mendefinisikan named actions: pembaca (viewer) menerjemahkan nama simbolik saat runtime alih-alih mengikuti tujuan yang disimpan. Empat nama didukung secara universal, dan semuanya adalah apa yang diharapkan pembaca dari bilah alat (toolbar): NextPage, PrevPage, FirstPage, dan LastPage. Karena tujuannya relatif terhadap halaman mana pun yang sedang ditampilkan pembaca, tombol Next yang dibuat dengan cara ini berfungsi di setiap halaman tanpa Anda harus menghitung target.
Di PDFlibPas, named action dilampirkan ke persegi panjang hotspot pada halaman saat ini. Argumen integer keempat dan kelima memilih kata kerja dan tampilannya.
// NamedActionType: 0 = NextPage, 1 = PrevPage, 2 = FirstPage, 3 = LastPage
// Options bit 0 (value 1) draws a border around the hotspot
Pdf.AddLinkToNamedAction(500, 560, 60, 18, 0, 1); // Next
Pdf.AddLinkToNamedAction(40, 560, 60, 18, 1, 1); // Previous
Pdf.AddLinkToNamedAction(110, 560, 60, 18, 3, 1); // jump to last page
Tidak ada tujuan yang perlu disinkronkan, dan itulah intinya. A named action bertahan dari penyisipan dan penghapusan halaman karena tindakan ini tidak pernah menamai halaman sejak awal. Kontraskan hal tersebut dengan tautan go-to eksplisit, yang menyimpan indeks halaman target yang harus Anda beri nomor ulang begitu dokumen bertambah besar.
Tindakan Hide dan jebakan array-nya
Tindakan Hide, ISO 32000-1 §12.6.4.10, Tabel 196, mengalihkan visibilitas dari satu atau beberapa field. Ini adalah cara terbersih untuk membangun perilaku tampilkan (show) dan sembunyikan (hide) tanpa skrip, dan merupakan hal yang Anda inginkan untuk tautan Tampilkan detail atau untuk dua panel yang saling eksklusif di mana menampilkan satu panel akan menyembunyikan panel lainnya. Tindakan tersebut membawa target di entri /T miliknya dan boolean /H yang menentukan arah: sembunyikan jika true, tampilkan jika false.
Kehalusannya sepenuhnya ada pada bagaimana target tersebut dikodekan, dan ini adalah jenis detail yang menghasilkan formulir yang berfungsi di mesin Anda tetapi gagal di mesin pelanggan. Ketika tindakan menamai satu field, /T ditulis sebagai satu string teks. Ketika menamai beberapa field, /T ditulis sebagai array string teks. Pembaca yang lebih lama tidak memperlakukan array satu elemen dengan cara yang sama seperti mereka memperlakukan string biasa, sehingga pengodean harus bercabang berdasarkan jumlahnya: satu nama harus dikeluarkan sebagai string, bukan sebagai array panjang satu, jika ingin dihormati oleh berbagai pembaca. PDFlibPas membuat keputusan itu untuk Anda. Anda meneruskan nama field yang dipisahkan oleh koma, titik koma, atau hentian baris, dan penulis mengeluarkan satu string untuk satu nama dan array untuk dua atau lebih.
// HideFlag non-zero hides the listed fields (/H true); zero shows them.
// One name -> /T is a text string. Two or more -> /T is an array of strings.
Pdf.AddLinkToHideField(40, 700, 90, 18, 'ShippingAddress', 1, 1);
Pdf.AddLinkToHideField(140, 700, 90, 18,
'ShippingName,ShippingAddress,ShippingZip', 1, 1);
Karena tindakan tersebut tidak mereferensikan sumber daya eksternal, tindakan ini tetap kompatibel dengan PDF/A. Nama-nama yang Anda berikan adalah judul field yang sepenuhnya memenuhi syarat (fully qualified), itulah sebabnya field anak (child) di dalam grup harus dipanggil melalui jalur titik lengkapnya (full dotted path) alih-alih hanya nama daunnya (leaf name) saja.
ImportData: mengisi data awal dari FDF
Ketika tindakan Hide mengatur ulang apa yang sudah ada di halaman, tindakan import-data membawa nilai dari luar halaman. ISO 32000-1 §12.6.4.8, Tabel 198, mendefinisikannya sebagai tindakan yang mengisi AcroForm dari file Forms Data Format di disk. Ini adalah tindakan di balik kontrol Muat ulang data sampel atau Reset ke default, di mana file FDF dikirim bersama PDF dan menyimpan nilai field kanonis. Panggilan ini mirip dengan yang lain, mengambil persegi panjang hotspot, jalur ke FDF, dan bitmask tampilan: Pdf.AddLinkToImportData(40, 660, 120, 18, 'defaults.fdf', 1). File tersebut tidak harus ada saat PDF dibuat, tetapi harus ada saat pengguna mengklik, dan setiap garis miring terbalik (backslash) di jalur tersebut akan ditulis ulang ke bentuk miring (slash) kanonis PDF untuk Anda.
Satu batasan perlu dinyatakan dengan jelas karena sering kali mengejutkan. Tindakan import-data menunjuk ke file eksternal, sehingga tidak diizinkan di PDF/A. Ketika dokumen berada dalam mode PDF/A, panggilan tersebut mengembalikan nilai nol dan tidak menambahkan apa pun alih-alih menghasilkan file yang gagal validasi. Jika pipeline Anda menargetkan keluaran arsip, pengisian awal harus dilakukan pada waktu pembuatan dengan menulis nilai field secara langsung, bukan menundanya hingga ada klik.
JavaScript: paket global dan skrip per tindakan
Untuk logika yang melampaui tampilkan, sembunyikan, dan impor, keluarga tindakan ini menggunakan JavaScript tingkat dokumen. Ada dua tempat berbeda di mana skrip dapat berada, dan perbedaannya sangat penting. Paket JavaScript tingkat dokumen disimpan sekali untuk seluruh berkas dan berjalan saat dokumen dibuka, menjadikannya tempat yang tepat untuk definisi fungsi dan keadaan bersama (shared state). Skrip per tindakan dilampirkan ke satu tautan atau field dan hanya berjalan saat objek tersebut diaktifkan, menjadikannya tempat yang tepat untuk satu baris kode yang memanggil fungsi yang telah ditentukan oleh paket tersebut.
PDFlibPas mengekspos keduanya. AddGlobalJavaScript menyimpan paket bernama di tingkat dokumen; menggunakan kembali nama yang sama akan menggantikan apa pun yang disimpan di bawahnya. AddLinkToJavaScript melampirkan skrip ke hotspot sehingga klik akan mengeksekusinya.
// Document-level package: define a reusable function once.
Pdf.AddGlobalJavaScript('Totals',
'function recalcTotal() {' +
' var net = this.getField("Net").value;' +
' var tax = this.getField("Tax").value;' +
' this.getField("Gross").value = Number(net) + Number(tax);' +
'}');
// Per-action script on a link: just call the shared function.
Pdf.AddLinkToJavaScript(40, 620, 100, 18, 'recalcTotal();', 1);
Menjaga fungsi tetap berada di paket global dan panggilan di tautan bukanlah preferensi gaya. Hal ini menghindari duplikasi kode yang sama pada setiap kontrol yang membutuhkannya, dan ini berarti pembaca dengan pembuatan skrip dinonaktifkan hanya tidak melakukan apa-apa saat klik alih-alih mengalami error pada blob inline yang cacat. Ini juga menjaga entri per tindakan tetap kecil, sehingga berkas tetap mudah dibaca saat Anda memeriksanya nanti.
Field, field anak, dan membekukan hasilnya
Tindakan memerlukan field untuk bertindak, jadi ada baiknya untuk melihat bagaimana field dibuat. NewFormField membuat field pada halaman saat ini dan mengembalikan indeksnya; tipe integer memilih jenisnya, di mana 1 adalah Text, 2 adalah Pushbutton, 3 adalah Checkbox, 4 adalah Radiobutton, 5 is Choice, 6 adalah Signature, dan 7 adalah Parent yang memiliki anak tetapi tidak menggambar apa pun sendiri. Judul yang Anda berikan tidak boleh mengandung tanda titik, karena tanda titik adalah pemisah dalam nama yang sepenuhnya memenuhi syarat yang digunakan oleh tindakan untuk memanggil anak-anaknya.
Grup radio dan formulir hierarkis dibangun dengan memberikan anak ke field induk (parent). NewChildFormField menambahkan anak di bawah induk bernama, dan untuk kasus radio serta pilihan, AddFormFieldSub menambahkan opsi individual dan mengembalikan indeks sementara yang Anda gunakan untuk memposisikan masing-masing opsi. Ketika fase interaktif selesai dan Anda ingin membekukan field sehingga tampilan saat ininya menjadi konten halaman permanen, FlattenFormField menggambar field tersebut ke halaman dan menghapusnya dari formulir. Setelah proses flatten (perataan), indeks field berikutnya akan bergeser ke bawah satu tingkat, yang merupakan satu hal yang harus diingat jika Anda meratakan beberapa field dalam sebuah perulangan.
var
Pdf: TPDFlib;
FldShip: Integer;
begin
Pdf := TPDFlib.Create;
try
Pdf.SetOrigin(1); // top-left origin
Pdf.SetPageSize('A4');
Pdf.NewPage;
// A text field the Hide action will target by its title.
FldShip := Pdf.NewFormField('ShippingAddress', 1);
Pdf.SetFormFieldBounds(FldShip, 40, 120, 240, 20);
Pdf.SetFormFieldValue(FldShip, '');
// Wire a Hide link and a navigation link to this page.
Pdf.DrawText(40, 110, 'Toggle shipping block:');
Pdf.AddLinkToHideField(220, 100, 70, 16, 'ShippingAddress', 1, 1);
Pdf.AddLinkToNamedAction(500, 800, 60, 18, 3, 1); // Last page
// A document-level script available to every event in the file.
Pdf.AddGlobalJavaScript('OnOpen',
'app.alert("Form ready", 3);');
// Freeze the field if the output should no longer be editable.
// Pdf.FlattenFormField(FldShip);
if Pdf.SaveToFile('form_actions.pdf') <> 1 then
raise Exception.Create('Save failed');
finally
Pdf.Free;
end;
end;
Panggilan flatten dinonaktifkan sengaja dengan komentar. Biarkan panggilan ini tidak aktif dan dokumen akan dikirim sebagai formulir aktif yang tindakannya berjalan di pembaca. Aktifkan panggilan ini dan field tersebut akan dirender menjadi tanda statis, yang merupakan apa yang Anda inginkan ketika formulir telah selesai dan hasilnya harus dikirim sebagai catatan tetap. Field yang sama, kode yang sama, menghasilkan dua dokumen yang sangat berbeda tergantung pada apakah Anda membekukannya.
Memilih kata kerja yang tepat
Keempat tindakan tersebut dibagi secara bersih berdasarkan apa yang disentuhnya. Named action memindahkan viewport dan tidak memerlukan field. Tindakan Hide mengubah visibilitas dan membutuhkan judul field, dengan pengodean string-versus-array yang ditangani secara otomatis untuk Anda. Tindakan import-data mengakses berkas di disk dan oleh karena itu dilarang di PDF/A. Tindakan JavaScript menjalankan logika acak dan paling baik dibagi antara paket fungsi global dan panggilan kecil per tindakan. Pilihlah tindakan paling sederhana yang dapat melakukan pekerjaan: tindakan Hide lebih portabel daripada skrip yang menyetel bendera tersembunyi (hidden flag), dan named action lebih tahan lama daripada tujuan halaman yang disimpan karena tidak ada nomor halaman yang perlu dipelihara.
Dari sini, dua topik terkait melengkapi gambaran ini. Jika formulir tersebut adalah bagian dari dokumen yang mudah diakses (accessible), pohon struktur yang ditelusuri oleh pembaca layar dibahas dalam artikel kami tentang struktur aksesibilitas dan PDF bertag. Ketika formulir yang sudah selesai harus dikunci dan ditandatangani, alur kerjanya dijelaskan dalam panduan meja kerja kepatuhan dan penandatanganan. Ketiganya dibangun di atas mesin yang sama, yang dikirim sebagai pustaka PDF untuk Delphi bersama dengan API pembuatan, formulir, dan tanda tangan yang dibahas di bagian lain di blog ini.