هذه الصفحة مترجمة من الإنجليزية. اقرأ الصفحة الإنجليزية
يحرّر Form::of(value) نموذجًا (model) أو كيانًا. تأتي الحقول من مراجع الحقول، والتسميات من ملفات اللغات، والتحقق من سمات #[rule]، ولذلك يفحص المتصفح والخادم القواعد نفسها.
نموذج
Form::of(Contact { quantity: 1, ..Contact::default() })
.row(|r| r.field(Contact::name).field(Contact::email))
.row(|r| r.field(Contact::quantity).field(Contact::size))
.submit(t!("docs.live.send"), send_contact)
.on_success(|_, ui| ui.toast(t!("docs.live.sent")))اترك الاسم فارغًا أو اكتب بريدًا إلكترونيًا خاطئًا واضغط Send: يعرض المتصفح الخطأ فورًا، وسيرفض الخادم المدخل نفسه بالرسالة نفسها.
التخطيط
| الاستدعاء | النتيجة |
|---|---|
.field(M::f) | حقل واحد في سطره |
.row(|r| r.field(a).field(b)) | حقول جنبًا إلى جنب؛ وتتراص فوق بعضها على الشاشات الضيقة |
.section(title) | مجموعة بعنوان للحقول التي تليه |
.step(title) | خطوة معالج؛ يعرض النموذج خطوة واحدة في كل مرة |
.range(M::from, M::to) | تاريخان كمنتقي نطاق واحد |
Form::auto(value) | كل الحقول، بترتيب الإعلان |
الحفظ
يسمّي .submit(label, action) الإجراء الذي يحفظ. يجب أن يأخذ الإجراء Valid<M> لنموذج النموذج نفسه؛ وأي شيء آخر لا يُترجم. يرسل وقت التشغيل القيم، ويشغّل الخادم كل قاعدة، ثم الإجراء.
- أخطاء الحقول من القواعد أو من
ValidationErrorsفي الإجراء تظهر بجوار الحقول. - يعرض
Error::Conflict(إصدار#[version]قديم) رسالة بأن شخصًا آخر غيّر السجل. - يشغّل
.on_success(|result, ui| ...)تأثيرات بعد حفظ ناجح (راجع التأثيرات والنوافذ).
عناصر التحكم
لكل نوع حقل عنصر تحكم افتراضي: حقول نص، وحقول أرقام، ومال مع العملة، ومنتقيات تاريخ ووقت، وخانات اختيار لـ bool، وقوائم منسدلة للاختيارات. يختار .control(M::f, Control::...) عنصرًا آخر، ويبحث .lookup(M::f, action) عن السجلات على الخادم. راجع عناصر التحكم والمنتقيات.
الحقول للقراءة فقط
حقول #[readonly] تُعرض في النماذج لكن لا يمكن تحريرها، مثل مستوى مخزون لا تغيّره إلا الحركات.
رفع الملفات
تحمل الحقول File وOption<File> وVec<File> ملفات يحتفظ بها FileStore. أعلن القواعد مرة واحدة على شكل const، وسجّل المخزن على الميزة مع صلاحية، واستخدم القواعد نفسها في النموذج:
pub const ATTACHMENTS: FileRules = FileRules::new("attachments")
.max_bytes(5 * 1024 * 1024)
.max_files(5)
.accept(&[FileType::Pdf, FileType::Png, FileType::Jpeg]);
Feature::new("support")
.files(ATTACHMENTS, "support.ticket.files", DiskStore::new("data/attachments"))
.scan_files(MyVirusScanner)
Form::of(ticket).field(Ticket::title).file(Ticket::attachments, ATTACHMENTS)عنصر التحكم حقل ملف أصلي داخل منطقة إفلات (drop zone). يُرفع كل ملف على حدة مع شريط تقدّم، ويمكن إلغاؤه أثناء عمله، وإعادة محاولته عند فشله، وإزالته عند اكتماله. تعرض الصور معاينة مصنوعة من عنوان كائن (object URL) للملف المختار. يُعلن التقدم والنتائج في منطقة حية (live region) مهذبة. في العرض التجريبي (showcase) مثال يعمل على /uploads؛ وموقع التوثيق هذا بلا مستخدم مسجّل الدخول، ولذلك لا يعرض رفعًا حيًا.
| المفتاح | ماذا يفعل |
|---|---|
| Tab | ينتقل إلى حقل الملف، ثم إلى رابط تنزيل كل ملف وزر Cancel أو Retry أو Remove الخاص به |
| Enter أو Space على حقل الملف | يفتح منتقي الملفات في النظام (مسار لوحة المفاتيح للسحب والإفلات) |
| Enter أو Space على Cancel أو Retry أو Remove | ينفذه؛ بعد Cancel ينتقل التركيز إلى Retry، وبعد Remove إلى الملف التالي أو يعود إلى الحقل |
ما يفعله الخادم لـ POST /_zeyn/files/{store}:
- يفحص ترويسة CSRF وصلاحية المخزن وحراس الميزة (guards)، ويشغّل المعترضات (interceptors) (
CallKind::Upload، مع تدقيق معرّف الملف الجديد)؛ وتُحسب عمليات الرفع ضمن حد الكتابة؛ - يرفض
Content-Lengthمعلنًا يتجاوز الحد (413)، ثم يقرأ أول 4 KiB ويستنتج النوع من المحتوى؛ يجب أن ينتمي الامتداد إلى النوع نفسه وأن يكون النوع ضمنaccept، وإلا415. لا يُقبل HTML وSVG وXML والسكربتات أبدًا. ويُتجاهل نوع المحتوى الذي يرسله المتصفح؛ - يبث الباقي إلى المخزن على دفعات لا تزيد على 64 KiB عبر قناة من أربع، ويعدّ البايتات؛ وتجاوز الحد أو انقطاع الاتصال أو 30 ثانية بلا بيانات توقف الرفع ويزيل المخزن ما كتبه؛
- يشغّل
FileScannerالخاص بالميزة؛ الملف المرفوض، أو ماسح يفشل، يُحذف ويكون الجواب422؛ - يجيب
201مع{id, name, size, type}. المعرّف 128 بتًا عشوائيًا من الخادم؛ وتُنظَّف الأسماء من المسارات وأحرف التحكم.
التنزيلات (GET /_zeyn/files/{store}/{id}) تفحص شكل المعرّف والصلاحية والحراس والمستأجر (tenant)، وتمر عبر خدمة الوسائط (راجع docs/media.md): النطاقات (ranges) وعلامات الكيان (entity tags) وnosniff. لا تُعرض مضمّنة إلا الصور النقطية والصوت والفيديو؛ وكل شيء آخر يُرسل بـ application/octet-stream مع Content-Disposition: attachment واسم ASCII بين علامتي اقتباس مع اسم UTF-8، تحت Content-Security-Policy: sandbox.
يحتفظ DiskStore بـ {id}.bin و{id}.json في مجلد واحد ويكتب عمليات الرفع في {id}.part أولًا. ويحتفظ MemoryStore بالملفات في الذاكرة بسقف بايتات، للاختبارات والعروض. قيمة النموذج تسمّي الملفات فقط: إجراء الحفظ الذي يجب أن يتأكد من وجود ملف يستدعي store.meta(id)، كما يفعل العرض التجريبي.
الميزانية: رفع بحجم 256 MiB يزيد كومة (heap) الخادم بنحو 0.4 MiB (tests/files_memory.rs)؛ ويعرض عنصر التحكم 10 عقد DOM مع 5 لكل ملف؛ ويضيف files.js 12 KB خامًا و4 KB بعد gzip، وfiles.css 3 KB خامًا و1 KB بعد gzip.