هذه الصفحة مترجمة من الإنجليزية. اقرأ الصفحة الإنجليزية
الحالة: منفّذة عبر zeyn::DataGrid ونقاط نهاية الشبكة في zeyn::server وassets/zeyn/*.js (تُقدَّم بوصفها /_zeyn/zeyn.js).
تُرسم الشبكة على الخادم. تأتي صفوفها من action تأخذ DataQuery وتُرجع Page<E>. يطلب وقت تشغيل المتصفح الصفحات، ويرسل ما يفعله المستخدم (الفرز والتصفية والتخطيط والتعديلات والإجراءات)، ويضع شيفرة HTML التي يتلقاها في مكانها. كل استدعاء يتحقق من الصلاحيات على الخادم، ولا يُوثق بأي شيء يرسله العميل.
DataGrid::of(search_products)
.mode(DataMode::Server)
.paging(Paging::pages(25))
.column(Product::code).width(120).pin_start().filter()
.link(|p| Window::open(product_editor, ProductParams { id: Some(p.id) }))
.column(Product::name).flex(1).filter().editable()
.column(Product::unit).filter().editable().badge(|u| match u {
Unit::Kg => Tone::Info,
_ => Tone::Neutral,
})
.column(Product::price).filter().editable().avg().max()
.column(Product::stock).filter().sum().progress(120)
.column(Product::active).filter().check_icon().editable()
.edit_with(save_product)
.row_action(t!("stock.restock"), restock_product, |p| p.id)
.bulk_action(t!("stock.deactivate"), deactivate_products, Product::id)
.row_menu(|p| ContextMenu::new()
.item(t!("stock.edit"), Window::open(product_editor, ProductParams { id: Some(p.id) }))
.action(t!("stock.restock"), restock_product, p.id))
.exportable()إجماليات الأعمدة
تعمل sum() وavg() على الأعداد الصحيحة والمبالغ المالية، وتعمل min() وmax() أيضًا على التواريخ والأوقات واللحظات، وتعمل count() على كل عمود. استخدامها مع نوع آخر لا يُترجم (لا يُصرَّف). وتغطي الإجماليات كل صف يطابق المرشّحات، لا الصفحة الحالية فقط.
تحسبها DataQuery::apply للبيانات في الذاكرة. أما طبقة البيانات التي تقسّم الصفحات في SQL فتقرأ query.aggregates وتضيف Page::with_total(field, op, value) من استعلام تجميعي واحد. وتحتفظ الشبكة فقط بالإجماليات التي تعلنها أعمدتها، فلا يستطيع العميل طلب غيرها. تُنسَّق الإجماليات على الخادم بلغة المستخدم. ولمتوسطات الأعداد الصحيحة منزلتان عشريتان (Value::Decimal)، وتُقرَّب متوسطات المبالغ المالية بعيدًا عن الصفر عند منتصف القيمة إلى أصغر وحدة.
المرشّحات والفرز
تضيف filter() مرشّحًا إلى صف المرشّحات، ويُختار حقل الإدخال بحسب نوع العمود:
| العمود | المرشّح | الاستعلام |
|---|---|---|
| نص | يحتوي، يبدأ بـ، يساوي | contains وstarts_with وequals |
| عدد صحيح، مبلغ مالي | من، إلى | from وto بصيغة 1234.56 |
| تاريخ | من، إلى | from وto بصيغة 2026-03-31 |
لحظة (DateTime) | من، إلى (تواريخ) | يوم كامل بالمنطقة الزمنية للمستخدم |
| وقت | من، إلى | 14:30 |
| اختيار | اختر أيًّا من الخيارات | any_of |
| نعم/لا | الكل، نعم، لا | is |
تنتقل القيم بصيغة آلية ثابتة مهما كانت لغة المستخدم. وما زال الشكل القديم { "field", "contains" } يعمل. يُسقط الخادم المرشّحات على الأعمدة التي بلا filter() والفرز على أعمدة لا تملكها الشبكة، ويضع حدودًا: المرشّحات (32) ونص المرشّح (200 حرف) والخيارات (100) ومفاتيح الفرز (4). والقيمة التي لا يمكن قراءتها تُتجاهل. وتأتي المنطقة الزمنية لمرشّحات التاريخ من إعدادات المستخدم على الخادم.
النقر مع Shift على الترويسة يضيف ذلك العمود إلى مفاتيح الفرز. ويُظهر رقم في الترويسة الترتيب.
الأعمدة: التثبيت وتغيير الحجم والإخفاء والنقل
تُبقي pin_start() وpin_end() العمود عند أحد الطرفين أثناء تمرير الشبكة جانبيًا (ثلاثة كحد أقصى لكل جهة). وتُخرج hidden() العمود حتى يُظهره المستخدم. لكل ترويسة قائمة عمود (الزر، أو النقر الأيمن، أو Shift+F10، أو مفتاح قائمة السياق) فيها الفرز والتثبيت والنقل يمينًا ويسارًا والإخفاء والمرشّح ومنتقي الأعمدة وإعادة الضبط. وحافة الترويسة تغيّر حجم العمود بالسحب أو بمفاتيح الأسهم (مع Shift لخطوات أكبر). وسحب ترويسة إلى ترويسة أخرى من مجموعة التثبيت نفسها ينقل العمود؛ أما الضغطة القصيرة فما زالت تفرز. وكل ذلك يعمل من اليمين إلى اليسار.
العروض المحفوظة
يشكّل تخطيط المستخدم (الأعمدة الظاهرة بترتيبها وعرض كل عمود والتثبيتات) والفرز والمرشّحات كائنًا من نوع DataGridView. ومع App::datagrid_views(store) يُحفظ لكل مستخدم على الخادم، ولكل مستأجر (tenant) وشاشة وشبكة، وتعود الصفحة به والصفحة الأولى مفروزة ومصفّاة أصلًا. نفّذ DataGridViewStore لقاعدة بيانات؛ أما MemoryViewStore فللتطوير والاختبارات. وبلا مخزن يحتفظ المتصفح بالعرض في localStorage. وينظّف الخادم كل عرض يتلقاه بأعمدة الشبكة وحدودها.
الخلايا
| الطريقة | الأعمدة | تعرض |
|---|---|---|
badge(|v| Tone) | تعدادات #[choice] | شارة ملونة لكل خيار |
progress(max) | الأعداد، المبالغ المالية | شريطًا والرقم |
link(|row| Effect) | أي عمود | القيمة رابطًا ينفّذ effect |
check_icon() | bool | علامة صح أو شرطة، مع كلمات لقارئات الشاشة |
relative_time() | DateTime | "قبل 3 دقائق"، والوقت الدقيق تلميحًا |
cell(renderer) | أي عمود | شيفرة HTML خاصة بك (CellRenderer<T>) |
يجب أن يُهرِّب CellRenderer بيانات المستخدم (escape). وتستخدم عمليات التصدير والنسخ القيمة الخام.
إجراءات الصفوف والقوائم والإجراءات الجماعية
تضيف row_action(label, action, |row| input) زرًّا إلى كل صف. وتضيف row_menu(|row| ContextMenu) قائمة سياق. وتضيف Screen::context_menu(...) واحدة إلى المساحة الفارغة من الشاشة. والعناصر التي تشغّل action لا تظهر إلا للمستخدمين الذين يملكون صلاحية تلك الـ action.
عندما يُشغَّل عنصر صف، لا يرسل العميل إلا مفتاح الصف والعنصر. يحمّل الخادم الصف من جديد عبر action الشبكة، ويبني العنصر من ذلك الصف، ويتحقق من صلاحية الـ action، ويشغّل المعترضات (interceptors) ثم الـ action. ويُرفض العنصر الذي لم يعد موجودًا للصف (مثلًا عنصر أصبح معطّلًا). ولا يأتي الإدخال من العميل أبدًا.
تضيف bulk_action(label, action, Model::key) زرًّا إلى شريط التحديد. تأخذ الـ action قيمة Vec<K> حيث K نوع حقل المفتاح؛ وأي شيء آخر لا يُترجم. واختيار "كل الصفوف المطابقة" يتيح للخادم جمع المفاتيح بنفسه بمرشّحات المستخدم، حتى 10,000. أما المفاتيح المختارة واحدًا واحدًا فتأتي من العميل، فعلى الـ action أن تتحقق من كل سجل كما تفعل action في API.
يحتفظ المتصفح بقائمة السياق الخاصة به في كل مكان آخر. تُوقفها App::browser_context_menu(false) إلا في حقول إدخال النص والنص القابل للتحرير. وهذا لا يحمي شيئًا: فما زال بإمكان المستخدمين قراءة مصدر الصفحة أو فتح أدوات المطوّر أو تعطيل السكربتات. كما أنه يزيل التدقيق الإملائي ونسخ الرابط والترجمة والأدوات المساعدة. أبقِ الإعداد الافتراضي.
التحرير المضمّن
استخدم edit_with(save) مع action تأخذ Valid<E> (القاعدة نفسها في النماذج) وeditable() على الأعمدة. لا تصبح المفاتيح والحقول للقراءة فقط وإصدارات الصفوف قابلة للتحرير أبدًا. المفتاح F2 أو النقر المزدوج على خلية قابلة للتحرير يبدأ تحرير الصف؛ ويحفظ Enter ويلغي Escape، ويتراجع Ctrl+Z أو زر الإشعار (toast) عن آخر حفظ.
يحمّل الخادم الصف من جديد بالمفتاح ويطبّق الأعمدة القابلة للتحرير وإصدار الصف فقط عبر Form::read، فتُقرأ القيم وتُتحقق كما يفعل النموذج. وقيمة لأي عمود آخر تُرفض بـ 403. والإصدار القديم يعود بـ 409، وتعود أخطاء التحقق بـ 422 وتظهر في خلاياها.
الرئيسي والتفصيلي
تتيح detail(|row| (screen, params)) لكل صف أن يتوسع إلى شاشة تُبنى من الصف، وتُحمَّل فقط عندما يفتحها المستخدم:
DataGrid::of(search_today)
.column(Order::id)
.detail(|o| (order_lines, OrderParams { id: o.id }))
#[screen(permission = "sales.order.read")]
pub fn order_lines(params: OrderParams) -> Screen {
Screen::window(t!("sales.order_lines", id = params.id.0.to_string())).body(
DataGrid::of(search_lines)
.scope(OrderLine::order_id, params.id)
.column(OrderLine::product)
.column(OrderLine::amount).sum()
.detail(|l| (product_editor, ProductParams { id: Some(l.product_id) })),
)
}تُكتب المعاملات (params) بنوع تحدده الشاشة (ScreenDef::Params)، فالمعاملات الخاطئة لا تُترجم. والتفصيل شاشة كاملة: نموذج، أو شبكة متداخلة، أو شبكة لها تفاصيلها الخاصة، إلى أي عمق. لا يظهر عمود التوسيع إلا للمستخدمين الذين يحق لهم فتح الشاشة، ويتحقق الخادم من صلاحية الشاشة مرة أخرى عند تحميل التفصيل. تنتقل المعاملات عبر المتصفح كما تنتقل معاملات النافذة، لذا يجب على شاشة التفصيل أن تتحقق من أن المستخدم يحق له رؤية السجل الذي تسمّيه.
تُبقي scope(field, value) الشبكة على الصفوف التي يساوي field فيها value في كل طلب. يأتي المرشّح أولًا في DataQuery::filters ولا يستطيع العميل إزالته، فتبقى الشبكة المتداخلة على صفها الأب مهما أرسل المتصفح. وطبقة البيانات التي تبني SQL من المرشّحات تقرؤه كأي مرشّح آخر (Filter::range بحدّين متساويين للأعداد والتواريخ، وequals للنص، وany_of للاختيارات، وis لنعم/لا، وblank للقيم الفارغة).
زر التوسيع، أو Enter عليه، أو Right وLeft على الصف تفتح التفصيل وتغلقه. والإغلاق يزيله، فيُحمَّل طازجًا في المرة القادمة.
تُبقي الشبكات الافتراضية (virtual) كل صف بارتفاع ثابت واحد حتى يمكن بلوغ أي موضع فورًا؛ ولا تعرض أزرار التوسيع. استخدم الصفحات أو الترقيم اللانهائي للشبكات ذات التفاصيل.
التجميع
تجمّع group_by(field) الصفوف على الخادم؛ استدعِها مرة أخرى للتجميع داخل كل مجموعة:
DataGrid::of(search_today)
.paging(Paging::pages(20))
.group_by(Order::city)
.group_by(Order::status)
.column(Order::amount).sum().avg()تعرض الشبكة صف ترويسة لكل مجموعة فيه تسمية العمود والقيمة وعدد الصفوف وإجمالي كل عمود على تلك المجموعة، تحت عموده. وفتح مجموعة (بزرها أو بـ Right وLeft) يحمّل مجموعاتها الفرعية أو صفوفها، صفحة صفحة مع صف "تحميل المزيد" إذا بقي المزيد؛ والإغلاق يزيلها. يقسّم المستوى الأعلى الصفحات على المجموعات، فيعدّ المرقّم المجموعات. ويحتفظ التذييل بالإجماليات على كل صف مطابق. وتحتفظ الصفوف داخل المجموعات بكل شيء آخر: التحرير وإجراءات الصفوف والقوائم والتحديد والتفاصيل.
تجيب طبقة البيانات عن DataQuery::group: فعند ضبطها تُرجع Page::groups (القيمة والعدد والإجماليات التي يطلبها الاستعلام، لكل مجموعة) مرتبةً بالقيمة وفق اتجاه مفتاح الفرز على ذلك الحقل، ومقسّمة إلى صفحات كالصفوف، مع total يعدّ المجموعات. تقوم DataQuery::apply بذلك للبيانات في الذاكرة؛ وتستخدم SQL عبارة GROUP BY. تُضبط group من الشبكة ولا تُقرأ من العميل أبدًا. لا يرسل العميل إلا مسار قيم المجموعات التي يفتحها؛ ويتحقق الخادم من طوله ويحوّل كل قيمة إلى مرشّح على عمود تلك المجموعة، قبل مرشّحات المستخدم (blank للمجموعة الفارغة).
بيانات الشجرة
تعرض tree(Model::parent) الصفوف شجرةً بحسب مفتاح أب اختياري: الصفوف بلا أب في الأعلى، ويُحمَّل أبناء الصف عند فتحه، صفحة صفحة، إلى 32 مستوى كحد أقصى.
DataGrid::of(search_employees)
.tree(Employee::manager_id)
.column(Employee::name)
.column(Employee::salary).sum()يحمل العمود الأول المسافة البادئة والزر. ولمعرفة أي الصفوف لها أبناء، تسأل الشبكة الـ action الخاصة بها مرة إضافية لكل صفحة: الصفوف التي يكون أبوها أحد مفاتيح الصفحة، مجمّعةً بحقل الأب (Filter::any_of على المفاتيح وDataQuery::group). تنطبق المرشّحات والفرز في كل مستوى؛ وتغطي إجماليات التذييل كل صف مطابق. لا تجمّع شبكات الشجرة ولا تعرض تفاصيل الصفوف. وبعد تحرير مضمّن يحتفظ الصف بمكانه في الشجرة.
لا تجمّع الشبكات الافتراضية ولا تعرض أشجارًا، لأن لكل صف فيها ارتفاعًا ثابتًا واحدًا.
الخلايا: التنقل والنسخ واللصق
Right على صف (أو نقرة على خلية) يضع مؤشر خلية فيه، كما في جدول بيانات. تحرّكه مفاتيح الأسهم، وتوسّع Shift مع سهم (أو Shift+نقرة) نطاقًا، ويذهب Home وEnd إلى طرفي الصف، وCtrl+Home وCtrl+End إلى أول خلية في الشبكة وآخرها. وينقل Tab وShift+Tab على امتداد الصف ثم إلى الصف التالي، ويغادران الشبكة عند طرفيها. وLeft على الخلية الأولى يعود إلى الصف. يحرّر Enter أو F2 خلية قابلة للتحرير، ويضغط Enter الزر الوحيد أو الرابط الوحيد في الخلية، ويُسقط Escape النطاق. وتحمل خلايا النطاق aria-selected.
ينسخ Ctrl+C على خلية النطاق نصًّا مفصولًا بعلامات جدولة، بالقيم كما يراها المستخدم. وما زال Ctrl+C على صف ينسخ الصفوف المحددة.
يلصق Ctrl+V على خلية نصًّا مفصولًا بعلامات جدولة قادمًا من جدول بيانات بدءًا من المؤشر؛ وقيمة واحدة تملأ النطاق كله. لا تتغير إلا الخلايا القابلة للتحرير (ويُبلَّغ بذلك عند اللصق في خلايا للقراءة فقط)، ويمكن لصق الاختيارات بتسميتها، ونعم ولا بكلمات المستخدم. يُحفظ كل صف تغيّر عبر مسار التحرير المضمّن (/_zeyn/grid/edit مع إصدار الصف)، صفًّا بعد صف، فيُقرأ كل صف ويُتحقق منه ويُفحص على الخادم كتحرير مضمّن؛ والصف الذي يفشل تظهر أخطاؤه في خلاياه وتُحفظ بقية الصفوف. أكثر من 10 صفوف تطلب تأكيدًا أولًا، وأكثر من 500 تُرفض، ويتراجع Ctrl+Z عن اللصق كاملًا. وتُحتسب تعديلات الشبكة وإجراءات الصفوف والإجراءات الجماعية وإجراءات القوائم ضمن App::write_limit، لذا قد يصطدم لصق كبير بحد الكتابة في التطبيق.
افتراضية الأعمدة
الشبكة ذات 40 عمودًا أو أكثر لا تُبقي في الصفحة إلا الأعمدة المثبّتة والأعمدة المرئية (إضافةً إلى ستة على كل جانب كحد أقصى). يحصل كل عمود على عرض ثابت، وتقوم خلية فاصلة واحدة مقام الأعمدة المتروكة على كل جانب، فتبقى عروض الأعمدة والتثبيتات والتمرير كما كانت. تحتفظ الصفوف بقائمتها الكاملة من الخلايا، فيرى النسخ واللصق والتحرير كل عمود، وتحمل الخلايا aria-colindex. ولا تتحرك النافذة إلا عندما يظهر عمود خارجها. ولا يزال الخادم يرسل كل خلية في الصف. والشبكة الافتراضية بهذا العرض (VIRTUAL_WIDE_COLUMNS، مع احتساب أعمدة التحديد والتفصيل وإجراءات الصف) ترسل كتلتها الأولى كاملة داخل <template data-zeyn-rows-later> بدل 64 صفًّا حيًّا، فلا يبني المتصفح أبدًا تخطيط صف بكل أعمدته: على الشبكة العريضة في المعرض انخفضت أطول مهمة عند البدء من 259 إلى 112 ms، وانخفض DOMContentLoaded من 402 إلى 228 ms (وسيط 8 تحميلات متناوبة، خادم إصدار (release)، جهاز مشغول).
قيس ذلك على الشبكة العريضة في المعرض (62 عمودًا و100,000 صف وتمرير افتراضي وخادم تصحيح (debug) ومتصفح Chromium ونافذة 1400 في 900 وشاشة 144 هرتز)، مع افتراضية الأعمدة وبدونها:
| الخلايا في الصفحة | جانبيًا، 60 بكسل لكل إطار (p50 / p95) | للأسفل، 60 بكسل لكل إطار (p50 / p95) | للأسفل، 2,000 بكسل لكل إطار (p50 / p95) | |
|---|---|---|---|---|
| بدونها | 2,976 | 6.9 / 7.5 ms | 42.7 / 64.8 ms | 78.5 / 141 ms، 178 مهمة طويلة |
| معها | 1,056 | 7.0 / 28.8 ms | 24.6 / 41.7 ms | 28.9 / 48.9 ms، 7 مهام طويلة |
التمرير للأسفل أسرع بنحو الضعف وتحمل الصفحة ثلث الخلايا. كان التمرير الجانبي سلسًا أصلًا؛ وتكلّف الإطارات التي تتحرك فيها النافذة حتى 29 ms. أما بقية الكلفة فهي تحليل كتل من 200 صف بكل أعمدتها، وهو ما لا تغيّره افتراضية الأعمدة.
التصدير
تتيح exportable() (واختياريًا export_permission(...) وexport_limit(n)) لزر Export::Csv في شريط الأدوات تنزيل كل صف مطابق. يقسّم الخادم الصفحات عبر action الشبكة ويبثّ كل صفحة فور وصولها، بتخطيط المستخدم ولغته. وتُبطَل الصيغ (formulas). حتى 100,000 صف افتراضيًا.
ومع ميزة Cargo المسماة xlsx ينزّل زر Export::Xlsx في شريط الأدوات الصفوف نفسها بوصفها مصنّف Excel (GET /_zeyn/grid/export?format=xlsx). تحتفظ الخلايا بأنواعها: الأعداد، والمبالغ المالية عددًا بصيغة عملتها، والتواريخ والتواريخ مع الوقت تواريخ Excel حقيقية بالمنطقة الزمنية للمستخدم، والقيم المنطقية. تُكتب الاختيارات بتسمياتها، والحقول المقنّعة بنصها المقنّع، والنص دائمًا نص، فلا يتحول شيء إلى صيغة. يحمل صف الترويسة التسميات بلغة المستخدم، وهو غامق ومثبّت، وتحتفظ الأعمدة بترتيب المستخدم وعروضه.
يُكتب المصنّف بـ rust_xlsxwriter في وضع الذاكرة الثابتة: تذهب كل صفحة إلى ملف مؤقت لحظة وصولها (صفحتان على الأكثر تنتظران في الذاكرة)، ويُبثّ المصنّف المضغوط إلى الاستجابة في النهاية. حافظ تصدير 100,000 صف من ستة أعمدة على الكومة (heap) عند نحو 134 KiB أثناء كتابة الصفوف (وهو الرقم نفسه بعد 10,000 وبعد 100,000 صف) و460 KiB أثناء الضغط؛ وكان الملف 3.4 MB.
ومع ميزة Cargo المسماة pdf يطبع Export::Pdf الصفوف نفسها بوصفها تقرير قائمة (2,000 صف على الأكثر)؛ انظر مستندات PDF.
التحديد
تضيف selectable() (تُفهم ضمنًا من bulk_action) عمود خانات اختيار. يحدّد Shift نطاقًا، وتحدّد Space الصف المركَّز، وتحدّد خانة الترويسة الصفحة. ويعرض شريط التحديد كل صف مطابق.
إمكانية الوصول
للجدول role="grid" وaria-rowcount (للشبكات الافتراضية) وaria-colcount وaria-colindex وaria-sort. تستخدم الصفوف نقطة توقف تبويب متنقلة (roving tab stop). ويمكن بلوغ الأزرار داخل الصف النشط بـ Tab. والشبكات المجمّعة وشبكات الشجرة هي role="treegrid" مع aria-level وaria-expanded على الصفوف. تستخدم القوائم role="menu" مع menuitem وmenuitemcheckbox وmenuitemradio، ويعود التركيز إلى موضعه السابق. وتُعلَن حالات الفراغ والخطأ (role="status").
الأداء
قيس على أرشيف المعرض (1,000,000 صف وتمرير افتراضي وخادم تصحيح (debug) ومتصفح Chromium وشاشة 144 هرتز) بواجهة Performance API:
| التمرير | الإطار p50 | الإطار p95 | الأقصى | المهام الطويلة |
|---|---|---|---|---|
| بسرعة العجلة (60 بكسل لكل إطار) | 13.9 ms | 20.9 ms | 34.6 ms | 0 |
| سريع (2,000 بكسل لكل إطار) | 14.1 ms | 27.8 ms | 54.9 ms | 0 |
| اجتياحات القائمة كلها | 13.9 ms | 27.8 ms | 48.6 ms | 0 |
يحوي DOM 62 صفًّا (نحو 660 عنصرًا)، وتبقى كومة JS قرب 4 MB. وتصدير 100,000 صف من الأرشيف نفسه بثّ 7 MB في 3.4 ثانية.
أبقِ باني الشاشة رخيصًا
الطلبات بلا حالة، لذلك يعيد كل طلب للشبكة بناء الشاشة التي تحوي الشبكة: كل كتلة تجلبها الشبكة الافتراضية (كل 200 صف يُمرَّر عليها)، وكل صفحة وفرز وترشيح وتصدير، وكل تحرير مضمّن وكل إجراء صف، يشغّل دالة #[screen] من جديد قبل أن يشغّل action الشبكة. يحتاج الخادم إلى الشاشة ليجد الشبكة بمسار عقدتها ويفحص الصلاحية، وهذا ثمن عدم الاحتفاظ بحالة لكل مستخدم.
الباني الذي يرتّب المكوّنات فقط كلفته ميكروثوانٍ. أما الباني الذي يستعلم من قاعدة البيانات فيدفع ذلك الاستعلام مع كل طلب من تلك الطلبات. لذلك:
- حمّل الصفوف في action الشبكة (
DataGrid::of(search)). فهو يعمل مرة واحدة لكل طلب على أي حال ويتلقىDataQueryبالصفحة والفرز والمرشّحات. - مكان الخيارات والبحوث هو نوع الحقل (
#[choice])، أو بحث النموذج (Form::lookup، الذي يسأل action الخاص به عندما يكتب المستخدم)، أو خدمة تحفظها في ذاكرة مؤقتة محدودة، لا استعلامًا يشغّله الباني. - عندما يجب أن تحمّل شاشة سجلًا (
Form::of(record))، أبعد عنها الشبكات الكبيرة أو الافتراضية: ضعها في شاشة خاصة بها، أو في شاشة تفصيل (DataGrid::detail) أو في نافذة، فتعيد طلبات كتلها بناء شاشة لا تحمّل شيئًا. - معاملات الشاشة (params) هي مدخلات الباني؛ أبقها مفاتيح فقط، ودع action الشبكة يرشّح بها (
DataGrid::scope).