مقال تقني

بناء حقول AcroForm والإجراءات باستخدام HotPDF في Delphi

يطرح فريق مطالبات نموذج موافقة مولَّدًا بلغة Delphi يبدو خاليًا من العيوب في Acrobat: كل خانة اختيار تُرسَم، وكل تسمية تتراصف، والتخطيط يطابق الأصل الورقي. وبعد ثلاثة أسابيع تبدأ خدمة الاستقبال برفض ثلث جميع عمليات الإرسال. يتوقّع الـ backend أن تُرسِل خانة الموافقة القيمة Y؛ بينما يصدّر النموذج Yes، لأن من بناه افترض أن التسمية وقيمة التصدير شيء واحد. لا شيء في الصفحة المرسومة يلمّح إلى الفرق. وهذه هي الخاصية المميِّزة لتطوير AcroForm: عنصر الواجهة المرئي والعقد البياني الكامن تحته بنيتان منفصلتان، ولا يُتحقَّق إلا من واحدة منهما حين يفتح أحدهم الملف وينظر إليه.

وHotPDF مكوّن VCL أصلي يكتب قواميس حقول AcroForm مباشرةً من شيفرة Delphi وC++Builder، فتكون كلتا البنيتين تحت تحكّم برمجي صريح — وكلتاهما قابلة للخطأ على نحو مستقل. وفيما يلي تغطية لإنشاء الحقول وإجراءات الأزرار وJavaScript على مستوى الحقل، مع التركيز على المواضع التي تتباعد فيها معاينة الصفحة وبيانات النموذج بهدوء.

أسماء الحقول مفاتيح توجيه، لا تسميات

يحمل كل حقل AcroForm اسمًا مؤهّلًا بالكامل (fully qualified name)، ويعرّف ISO 32000-1 §12.7.3 ذلك الاسم — لا التسمية المرئية أبدًا — بوصفه المفتاح الذي تنتقل تحته قيمة الحقل في عمليات الإرسال عبر FDF وXFDF وHTTP. ويترتّب على ذلك نتيجتان تفاجئان عادةً المطوّرين القادمين من تصميم نماذج VCL، حيث لا يكون اسم عنصر التحكّم أكثر من معرّف في الشيفرة.

أولًا، الحقلان اللذان يحملان الاسم المؤهّل بالكامل نفسه ليسا حقلين. يعاملهما نموذج PDF بوصفهما تعليقَي widget لحقل واحد يتشاركان قيمة واحدة: اكتب في أحدهما فيتحدّث الآخر فورًا. وتكرار اسم العميل في كل صفحة من عقدٍ ما استخدامٌ مشروع لهذا السلوك. أما الحصول عليه بالصدفة، لأن حلقة توليد أعادت استعمال 'Field1' عبر ثلاث صفحات، فهو خطأ لا يلتقطه أي فحص بصري — إذ تظل كل صفحة تعرض عنصرها الخاص، ولا يظهر التطابق المنعكس إلا حين يبدأ المستخدم بالكتابة.

ثانيًا، تبني الأسماء المنقّطة مثل applicant.email تسلسلًا هرميًا: تجمع العقدة الأمّ applicant أبناءها لعمليات إعادة التعيين والإرسال التي تستهدف جزءًا من النموذج فقط. وتسمية الحقول هرميًا منذ البداية لا تكلّف شيئًا وتؤتي ثمارها أول مرة يطلب فيها النظام المستقبِل «كتلة مقدّم الطلب وحدها».

وتضيف أزرار الاختيار (radio buttons) قاعدة ثالثة: الأزرار التي يُفترض أن تتبدّل بوصفها مجموعة واحدة يجب أن تتشارك اسم مجموعة واحدًا. في HotPDF، تُلحِق استدعاءات AddRadioButton التي تستعمل اسم المجموعة نفسه عناصرها بحقل أمّ واحد، وتُحدِّد قيمة التصدير لكل زر — '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 نزولًا من الأعلى، تنتج بصورة شبه حتمية مسودة أولى يكون فيها كل حقل منعكسًا رأسيًا. حوّل الإحداثيات في دالة مساعِدة مشتركة واحدة بدلًا من كل موضع استدعاء، كي يسري الإصلاح في كل مكان دفعةً واحدة.

ربط الأزرار بإجراءات URI وJavaScript والإرسال

لا يفعل زرّ الضغط (push button) شيئًا حتى يُربط به إجراء. يكشف HotPDF أنواع الإجراءات المعرّفة في ISO 32000-1 §12.6.4 عبر التعداد THPDFButtonActionbaURI و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]);

تستحقّ رايات الإرسال (submit flags) اهتمامًا تصميميًا أكبر مما تناله عادةً. تأخذ AddPushButtonWithSubmitAction مجموعة THPDFSubmitFormFlags، وتنتج المجموعة الفارغة طلب post مرمَّزًا بصيغة url عادية — وهي الصيغة التي تقبلها كثير من نقاط النهاية التجريبية ولا تقبلها كثير من نقاط نهاية الإنتاج. وإضافة sffXFDF تحوّل الحمولة إلى XFDF؛ وsffGetMethod تغيّر فعل HTTP؛ وsffIncludeNoValueFields تجعل الحقول الفارغة تظهر في الحمولة بدلًا من إسقاطها بصمت، وهو ما يهمّ كلما ميّز المستهلِك بين «غائب» و«فارغ». ومجموعة الرايات هي عمليًا جزء من عقد الواجهة مع نقطة النهاية المستقبِلة، فاخترها بالاشتراك مع الفريق الذي يحلّل عملية الإرسال — لا بعد أول دفعة مرفوضة.

JavaScript على مستوى الحقل: keystroke وformat وvalidate

إلى جانب نقرات الأزرار، يربط HotPDF شيفرة JavaScript بأحداث الحقل التي تطلقها العارضات القادرة على تشغيل السكربتات أثناء إدخال البيانات. وتقابل المحفّزات الثلاثة لحظات مختلفة في دورة حياة الإدخال: تعمل إجراءات keystroke مع وصول الحروف، وتعيد إجراءات format كتابة القيمة المعروضة بعد اعتماد التغيير، وتقبل إجراءات validate القيمة المعتمَدة أو ترفضها.

// 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 داخل سكربت keystroke أو validate يجعل العارض يرفض الإدخال. والقيد الذي ينبغي تذكّره: لا تُنفَّذ هذه الشيفرة إلا في العارضات التي تتضمّن محرّك JavaScript. فـ Acrobat وقلّة من منتجات سطح المكتب تشغّلها؛ بينما تتجاهلها كليًا معظم القارئات على الأجهزة المحمولة والعارضات المضمّنة في المتصفح ومسارات الطباعة. ترفع سكربتات الحقول جودة البيانات للمستخدمين الذين تصلهم — لكنها ليست حدًّا أمنيًا، وكل قيمة مُرسَلة ما زالت بحاجة إلى تحقّق على الخادم حين تصل.

العيوب التي تجتاز المراجعة البصرية

بعض عيوب النماذج غير مرئية بنيويًا لفحص «افتح وانظر». وتمثّل هذه الأربعة معظم التصعيدات المتعلقة بـ AcroForm التي تصل إلى الدعم، ويمكن التقاط كل منها آليًا قبل الإصدار:

  • انحراف قيمة التصدير. خانة اختيار أُنشئت بالشكل AddCheckBox('consent', 'Yes', ...) تُرسِل Yes؛ ومستهلِك يطابق على Y يرفض كل عملية إرسال بينما تبدو الصفحة مثاليةً. صدّر النموذج المعبّأ بصيغة XFDF من Acrobat وقارن القيم بمخطّط المستهلِك.
  • التطابق المنعكس العَرَضي للقيم. الأسماء المؤهّلة بالكامل المكرّرة تدمج الحقول في حقل واحد. ويظهر العَرَض وقت إدخال البيانات، لا وقت التوليد أبدًا، فاختبر بالكتابة في النموذج — لا برسمه.
  • قيم القائمة المنسدلة خارج قائمة الخيارات. إذا كانت القيمة الحالية المُمرَّرة إلى AddComboBox ليست ضمن الخيارات، تختلف العارضات فيما إذا كانت ستعرضها أو تفرّغها أو تشير إليها كمشكلة. أبقِ القيمة الافتراضية داخل القائمة.
  • حقول ما زالت قابلة للتحرير بعد إغلاق سير العمل. لا يملك HotPDF استدعاءً لتسطيح مظهر حقول AcroForm؛ والطريقة المدعومة لتجميد نموذج مكتمل هي إنشاء الحقول براية ffReadOnly، التي تُبقي القيمة مرسومةً عبر appearance stream الخاص بالحقل مع منع التحرير. ويظل الحقل للقراءة فقط كائن نموذج حيًّا، تتعامل معه أدوات التجميع والتوقيع اللاحقة على نحو يمكن التنبّؤ به.

وثمة سلوك من جهة العارض يستحق ملاحظة انحدار رغم أنه لا يوجد تغيير في الشيفرة يصلحه: يمكن لعمليات نشر Acrobat في المؤسسات تعطيل JavaScript أو تقييد أهداف الإرسال بحسب السياسة، فقد يكون إجراء عمل طوال التطوير خاملًا على سطح مكتب عميل. امنح عمليات الإرسال بديلًا مرئيًا — تعليمات مطبوعة على الأقل — للحالة التي لا يفعل فيها الزر شيئًا.

أين يتصل عمل النماذج ببقية المستند

حقل التوقيع نفسه نوع من حقول AcroForm، فالنموذج الذي سيُعتمَد أو يُوقَّع لاحقًا من طرف مقابل ينبغي أن يحجز ذلك الحقل أثناء التوليد بدلًا من ترقيعه بعد ذلك؛ والآليات على مستوى البايت مغطّاة في المقالة المرافقة حول التوقيعات الرقمية والتوقيع بمعيار PAdES باستخدام HotPDF. وإذا وصلت مدخلاتك على هيئة حزم XFA بدلًا من AcroForm الأصلي، فإن تسطيح XFA إلى حقول AcroForm سير عمل منفصل له نموذج فقدان خاص به، لأن تقنيتَي النماذج حصريتان متبادلتان داخل ملف واحد.

إن دوال الحقول والإجراءات والمحفّزات المعروضة هنا جزء من واجهة برمجة التطبيقات القياسية لـ HotPDF Component الخاصة بـ Delphi وC++Builder؛ وتربط صفحة المنتج المرجع الكامل، بما في ذلك أحمال دالة رايات الحقل والتعداد الكامل لرايات الإرسال.