إنتقل إلى المحتوى الرئيسي

اجعل الشاشة تتفاعل

الهدف​

اجعل الشاشة المرسومة تفعل شيئًا: عنوانًا يقول الاسم الذي كُتب، ولوحة تظهر عند تعليم مربّع، وزرّ حفظ لا يعمل إلا إذا امتلأ النموذج.

لا شيء من ذلك رسمٌ، ولا شيء منه شفرة تكتبها في ملف. إنه الحالة — تلك الأشياء القليلة التي تتغيّر أثناء استخدام الشاشة — والارتباطات، وهي تعبيرات من سطر واحد تقرأ تلك الحالة.

الخطوة ١ — امنح الشاشة حالة​

  1. افتح الشاشة في المصمّم (الشاشات ← اختر واحدة ← تصميم).
  2. انقر إطار الشاشة نفسه — الصندوق الخارجي — وافتح تبويب الشاشة في الفاحص على اليمين.
  3. تحت حالة الشاشة، اضغط إضافة حقل حالة.
  4. اكتب اسمًا — مثل requester — وقيمة أولية.

القيمة الأولية تعبير لا قيمة مكتوبة، فالصندوق الفارغ يبدأ بـ "" (علامتَي اقتباس)، ومربّع الاختيار بـ false، والعدّاد بـ 0. إنها اللغة نفسها التي يُكتب بها كل شيء آخر في هذه الشاشة، ولهذا لا يوجد سوى شيء واحد لتتعلّمه.

أضف ما تحتاجه الشاشة. أغلب الشاشات تحتاج حقلين أو ثلاثة.

لماذا تنتمي الحالة إلى الشاشة

لأن أداتين تقرآن الحقل نفسه. لو كانت الحالة تخصّ الصندوق الذي أضفتها منه، لأخذ حذفُ ذلك الصندوق الحقلَ معه — ولتوقّف كل ما يقرأه بصمت.

الخطوة ٢ — اربط خاصية​

  1. اختر أي كائن على اللوحة.
  2. في تبويب الشاشة، تحت الارتباطات، اختر خاصية من اربط خاصية.
  3. يظهر محرّر بداخله مثال. استبدله بتعبيرك.

أربع خصائص قابلة للربط:

الخاصيةما تقودهعلى
النصما تقوله — كلمات عنوان، تسمية زر، النص الإرشادي في صندوقأي شيء
الظهورهل هي على الشاشة أصلًاأي شيء
مفعّلهل يمكن استخدام الأداةالأدوات
القيمةما تحمله الأداةالأدوات

كل شيء آخر في الكائن — موضعه وحجمه ولونه — قد رسمته، والرسم هو الطريقة الأفضل لقول ذلك كله.

أمثلة تؤدي عملًا حقيقيًا:

state.requester نص عنوان
"Hello, " + state.requester الشيء نفسه مع كلمات حوله
state.showDetails ظهور لوحة
!state.showDetails ظهور السطر الذي يقول إنها فارغة
state.requester != "" تفعيل زر الحفظ
screen.width > 700 ظهور شيء لا تتّسع له نافذة ضيّقة

المحرّر هو محرّر BS Lang في المنصّة، فيضع خطًا تحت ما لا يفهمه ويقترح أثناء الكتابة. إن ظهر الخط، مرّر فوقه لتقرأ السبب.

الخطوة ٣ — دع الأداة تكتب​

اربط قيمة صندوق نصّي بـ state.requester، فتصبح الكتابة فيه كتابةً في state.requester. وكل ما يقرأ ذلك الحقل يتبعه فورًا — التحية، وزر الحفظ، وكل شيء.

هذا كل ما يجعل الشاشة حيّة، ولا يحتاج إلى أي معالج.

قاعدة واحدة: لا تحدث الكتابة العكسية إلا حين تكون القيمة مسار حالة بسيطًا — state.requester أو state.form.name. أما إن كانت محسوبة (state.first + " " + state.last) فتعرض الأداة القيمة وللقراءة فقط، إذ لا سبيل للرجوع ممّا كتبه أحدهم إلى الحقلين اللذين جاء منهما.

الخطوة ٤ — اجعل شيئًا يحدث عند الضغط على زر​

  1. حدّد الزر.
  2. تحت عندما يحدث هذا، اختر عند الضغط من التعامل مع حدث.
  3. اكتب ما ينبغي أن يحدث — تعليمة في كل سطر:
notify("Ticket raised for " + getState("requester"));
setState("requester", "");
setState("showDetails", false);

ما يمكن للمعالج استدعاؤه:

الاستدعاءما يفعله
setState("name", value)يغيّر حالة الشاشة — وكل ما يقرؤها يتبعها
getState("name")يقرؤها، لتحسب من الموجود
loadRecords("table", "field")يحمّل الصفوف من جدول إلى حقل حالة — انظر أدناه
getRecord("table", id, "field")يقرأ سجلًا واحدًا بمعرّفه
createRecord("table", { … }, "field")ينشئ سجلًا جديدًا؛ والوسيط الثالث اختياري
updateRecord("table", id, { … })يغيّر الحقول التي تسمّيها في سجل واحد
deleteRecord("table", id)يحذف سجلًا واحدًا
callApi("GET", "psl/v1/overview", { … }, "field")يسأل Custom API ويحتفظ بإجابته — انظر أرقام من Custom API
notify("…") / notifyError("…")يخاطب مستخدم الشاشة
showModal("بطاقة") / closeModal()يعرض بطاقة تراكب فوق الشاشة — انظر الخطوة ٥
openStartDrawer("بطاقة") / closeStartDrawer()يُنزلق واحدة من حافة البداية
openEndDrawer("بطاقة") / closeEndDrawer()ومن حافة النهاية
openDropdown("بطاقة") / closeDropdown()يفتح واحدة بجانب ما ضُغط عليه
setVisualState("name")يضع أقرب كائن فوق هذا المعالج رُسمت عليه حالة في تلك الحالة؛ و "" يعيده إلى العادي — انظر إعادة استخدام تصميم بالمكوّنات
openUrl("…", "newTab")ينتقل إلى عنوان وِب؛ احذف الوسيط الثاني ليفتح في هذه النافذة
navigate("Screen")ينتقل إلى شاشة أخرى من هذا التطبيق — بالاسم المكتوب على إطارها
reloadSurfaces()يعرض كل جدول أو نموذج مضمّن في هذه الشاشة من جديد ببيانات محدَّثة

المعالج برنامج صغير لا تعبير. يجوز له أن يغيّر الأشياء — وهذا كل الغرض منه — وله ما يحتاجه من أسطر. وهذا هو الفرق عن الارتباط الذي يقرأ فقط.

وبقية الأحداث حاضرة حين تحتاجها: عند الضغط مرتين، وعند تغيّر القيمة (على أداة)، وعند التركيز، وعند فقد التركيز، وعند وصول المؤشّر، وعند مغادرة المؤشّر — وعلى إطار الشاشة نفسه عند فتح الشاشة، الذي يُنفَّذ مرة واحدة بعد وجود الحالة.

لتغيير الشكل وحده، ارسم حالة

المعالج الذي يغيّر لونًا فحسب يكلّف عملًا مع كل حركة للمؤشّر. أما الحالة المرسومة فلا تكلّف شيئًا، لأن المتصفّح هو من ينفّذها. انظر إعادة استخدام تصميم بالمكوّنات.

الانتقال إلى شاشة أخرى من التطبيق​

يضمّ التطبيق عدّة شاشات، وnavigate هو ما ينقلك بينها:

navigate("Details");
navigate("Details", { id: getState("chosen") });

الاسم هو الاسم المكتوب على إطار الشاشة، كما كتبتَه هناك تمامًا — فإذا غيّرت اسم الإطار توقّف كل navigate يسمّي الاسم القديم، وتقول الشاشة ذلك عند الضغط بدل أن تفشل بصمت.

ما يبقى بعد الانتقال وما لا يبقى. تُغلق الشاشة التي تغادرها: تذهب حالتها معها، فالعودة إليها تعني حالة جديدة — النموذج الذي تركته نصف ممتلئ لا ينبغي أن يظلّ كذلك، والشاشة التي حمّلت سجلات عند فتحها تحمّلها من جديد. ما يسافر معك هو ما تسلّمه بيدك: يصل الوسيط الثاني بوصفه screen.params، فتقرأ screen.params.id في الشاشة التالية ما أرسلته.

إنها دائمًا screen.params. لا يوجد params أقصر، ويضع محرّر الشيفرة خطًا تحت أي واحد منها.

وشريط العنوان يتبعك. لكل شاشة عنوانها، فيعيدك زرّ الرجوع في المتصفّح إلى الشاشة السابقة، ويعود التحديث إلى الشاشة نفسها، ويفتح الرابط الذي تنسخه عليها. أما القيم المسلَّمة فلا تسافر بهذه الطريقة — فالوصول بالرجوع أو بالتحديث أو برابط ملصوق يفتح الشاشة بلا screen.params — فاعتبرها تسليمًا بين شاشتين لا شيئًا يحمله رابط.

قراءة السجلات وكتابتها​

خمسة نداءات، وهي مجتمعةً ما يحوّل الرسم إلى تطبيق.

قراءة قائمة:

loadRecords("par_ticket", "tickets");
loadRecords("par_ticket", "tickets", 20);
loadRecords("par_ticket", "tickets", { top: 20, where: { par_status: "Open" }, orderBy: "par_name" });

تحلّ الصفوف في حقل الحالة الذي سمّيته، وبقية الشاشة تعرف أصلًا ما تفعل بها — جدولٌ يتكرّر على state.tickets، ونصٌّ يقرأ state.tickets.length(). انظر ارسم صفًّا واحدًا، تحصل على واحد لكل سجل.

الوسيط الثالث إمّا كم على الأكثر، وإمّا السؤال كلّه: top وwhere وorderBy وdescending. وwhere يقارن بالمساواة فقط، وكل زوج تكتبه يجب أن يتحقّق. وحين تحتاج أكثر من ذلك — مدى، أو «يحتوي»، أو أو — فابنِ عرضًا بمصمّم الاستعلام وارسم الشاشة عليه بدل حشر ذلك في سطر واحد هنا.

قراءة سجل واحد:

getRecord("par_ticket", screen.params.id, "ticket");

هذا قرين navigate("Details", { id: row.id }) — شاشة القائمة تسلّم المعرّف، وشاشة التفاصيل تقرأ السجل خلفه. وإن لم يعد ذلك السجل موجودًا بقي الحقل كما كان ولم يُفرَّغ، فتظلّ الارتباطات تحته تعمل.

الكتابة:

createRecord("par_ticket", { par_name: getState("title"), par_status: "Open" });
createRecord("par_ticket", { par_name: getState("title") }, "created");
updateRecord("par_ticket", getState("ticket").id, { par_status: "Closed" });
deleteRecord("par_ticket", getState("ticket").id);

الوسيط الثالث لـcreateRecord اختياري ويستحقّ الكتابة غالبًا: تجيب المنصّة بالسجل الذي خزّنته، بمعرّفه وكل شيء، فيكون getState("created").id هو سبيل الشاشة إلى فتح ما أنشأته للتوّ أو تحديثه. وبدونه لا سبيل إلى معرفة ذلك المعرّف.

وأسماء الحقول هي الأسماء المنطقية لأعمدة الجدول — par_name لا الاسم — وهي نفسها التي يعرضها مصمّم الجداول.

قل شيئًا بعدها

لا شيء يخبر مستخدم الشاشة أن الكتابة نجحت، فقلها أنت: createRecord(…); notify("سُجِّل الطلب");. وإن رفضت المنصّة الكتابة — حقل مطلوب، أو صلاحية — عرضت الشاشة السبب الذي أعطته، ولذلك تستحقّ تلك الرسالة أن تُقرأ لا أن تُستبدل. ثم يتوقّف باقي ذلك النقر، فلا يحدث notify أو navigate بعد الكتابة إلا إذا نجحت.

ما يحقّ للشاشة فعله يقرّره الخادم

كلّ واحد من هذه يُفحص مقابل صلاحيات من يستخدم التطبيق، في كل نداء. وإخفاء زر الحذف يخفي زرًّا؛ ولا يمنع شيئًا.

لإرسال أحدهم إلى صفحة، ارسم رابطًا

‎openUrl‎ لِما تقرّره الشاشة — بعد الحفظ، أو بحسب ما اختِير. أما حين تكون الوجهة معروفة فارسم رابطًا: النقر الأوسط، والنقر الأيمن ← فتح في تبويب جديد، وقائمة روابط قارئ الشاشة، كلّها تعمل على رابط حقيقي ولا تعمل على غيره. انظر ارسم شاشة استوديو.

أرقام من Custom API​

الرقم — حجوزات اليوم، إيراد هذا الشهر — نطاقٌ ومجموع، غالبًا عبر عدة جداول، ولا يستطيع loadRecords أن يطلبه. احسبه في Custom API واستدعه من الشاشة:

callApi("GET", "psl/v1/overview", { date: "2026-10-06" }, "figures");

العنوان هو عنوان الـAPI نفسه داخل البيئة: بادئة الناشر والإصدار والمسار. في GET تُرسل المدخلات سلسلةَ استعلام، وفي POST وغيره جسمًا للطلب. تصل الإجابة إلى حقل الحالة الذي تسمّيه، فيعرضها نصٌّ مربوط بـstate.figures.bookingsToday. يعمل الـAPI بصلاحيات من يستخدم الشاشة.

ليس في البوابة

صفحة البوابة تقرأ الجداول التي تسردها بوابتها ولا تسمّي API أبدًا، فيعرض callApi هناك رسالة بدل الإجابة.

حين يضيق المربع: صفحة الكود​

الارتباط سطر واحد ومكانه بجانب ما يقوده. أما المعالج فيكبر — وبعد ثالث ‎if‎ يصير محرّر اللوحة الصغير شقًّا ضيّقًا.

حدِّد الكائن أولًا، ثم اضغط كتابة كود في شريط الأدوات (زر </>). يُغطّى المصمِّم — وتبقى الصفحة حوله كما هي — بقائمة على الجانب بكلّ ما يمكن كتابته على ذلك الكائن، ومحرِّر فيه متّسع للكتابة. تضمّ القائمة ارتباطاته ومعالجاته وحقول حالة الشاشة وخصائص المكوّن نفسه. وتُسرد أيضًا الخانات التي لم يُكتب فيها شيء، وهو أقصر جواب عن «ماذا يمكنني أن أكتب هنا؟».

والقائمة تقول أيّها مكتوب، فلا تحتاج أبدًا إلى فتح واحدة لتعرف:

بجوار الاسميعني
نقطة خضراء والاسم عريضمكتوب وسليم
نقطة حمراء والاسم عريضمكتوب وفيه خطأ — افتحه فيضع المحرِّر خطًّا تحت موضعه
نقطة مجوَّفة والاسم باهتلم يُكتب فيه شيء بعد

وما دامت صفحة الكود مفتوحة فلوحة المفاتيح للمحرِّر. كتابة t تكتب t؛ ولا تمتدّ إلى أداة النص خلف الصفحة.

وللعودة اضغط تصميم — الزر نفسه الذي يعيدك من المعاينة، وفي الموضع نفسه أعلى الصفحة. والرسم كما تركته تمامًا: التحديد نفسه ومستوى التكبير نفسه. وهو المستند نفسه في الحالتين، فالمعالج الذي تكتبه هنا تعرضه اللوحة هناك.

وإن لم يحدث شيء عند الضغط، فرسالة تقول السبب، والأسباب ثلاثة لا غير:

الرسالةما العمل
لا شيء محدَّد — أو أكثر من شيء محدَّدحدِّد كائنًا واحدًا بالضبط
الكود يُكتب على المكوّن نفسهلديك نسخة أو حالة من مكوّن. انقر بالزر الأيمن ← الذهاب إلى المكوّن الأصلي واكتبه هناك — يصل إلى كل النسخ
هذا الكائن جزء من رسم الشاشةقل أولًا ما هو في تبويب الشاشة — زر، مربّع نص — عندها تكون له معالجات تُكتب

كود المكوّن يُكتب على أصله. حدِّد الإطار الذي صُنع منه المكوّن — لا نسخة منه على شاشة — فتقدّم القائمة عند الضغط وبقيّتها، تمامًا كأي كائن آخر. هناك مكان سلوك المكوّن: اكتبه مرة واحدة، فتفعله كل نسخة على كل شاشة.

الخطوة ٥ — اسأل قبل أن تفعل، في نافذة أو قائمة منسدلة أو لوحة جانبية​

بطاقة التراكب قطعة من الشاشة ليست موجودة حتى يعرضها معالج.

  1. ارسم محتواها مرة واحدة كمكوّن. بطاقة فيها سؤال وزرّان مكوّن كأي مكوّن آخر — انظر إعادة استخدام تصميم بالمكوّنات — فيمكن استخدام التأكيد نفسه في كل شاشة تحتاجه. ارسمها بجانب شاشتك حيث يوجد متّسع.

  2. حدّد المكوّن وعلّم بطاقة تراكب في تبويب الرسم. وهذا كل الإعداد: يكفّ عن أن يُرسم على الشاشة، ويصير شيئًا يستطيع معالج أن يعرضه.

  3. اعرضها من معالج، باسم المكوّن نفسه:

    الاستدعاءأين تظهرتصلح لـ
    showModal("بطاقة التأكيد")وسط الشاشة فوق خلفية معتمةسؤال يجب الإجابة عنه قبل أي شيء آخر
    openDropdown("بطاقة القائمة")ملاصقة لما ضُغط عليهقائمة صغيرة، تلميح، نموذج قصير
    openStartDrawer("بطاقة المرشّحات")بكامل الارتفاع من حافة البدايةالمرشّحات، قائمة على نافذة ضيّقة
    openEndDrawer("بطاقة التفاصيل")بكامل الارتفاع من حافة النهايةلوحة تفاصيل
  4. أغلقها بـ closeModal() أو closeDropdown() أو closeStartDrawer() أو closeEndDrawer() — من زر بداخلها مثلًا. الدرج ليس له زر إغلاق خاص به: ارسم واحدًا في البطاقة، كما تفعل قوائم العينات. ما يزال Escape والنقر بجانب الدرج يغلقانه.

بطاقة يجب الإجابة عنها​

تحت تأشير بطاقة تراكب تأشيرٌ ثانٍ: يجب الإجابة عنها. وبتفعيله، ما دامت البطاقة مفتوحة لا يعمل النقر خارجها ولا مفتاح Escape ولا زر الإغلاق — بل لا يُعرض زر الإغلاق أصلًا، لأن عنصر تحكّم ظاهرًا لا يفعل شيئًا يبدو كصفحة متجمّدة لمن يضغطه مرتين.

اتركه مطفأً إلا إذا كانت البطاقة تسأل ما لا يجيب عنه الانصراف. التراكب الذي فتحه أحدهم بالخطأ ينبغي أن يُغلق كما فُتح؛ والنوع الوحيد الذي يجب ألّا يُغلق هو ذاك الموجود لأخذ إجابة — «أتُحذف هذه السجلات الأربعة؟»، «غيّر هذا السجل شخص آخر، أيّهما يبقى؟» — حيث لا يكون النقر على الخلفية إجابةً ويُقرأ مع ذلك إجابةً.

ارسم مخرجك بنفسك. البطاقة الإلزامية ترفض كل وسيلة إغلاق توفّرها المنصّة، فتحتاج زرًّا خاصًّا بها ينفّذ closeModal() أو closeStartDrawer() أو closeEndDrawer(). ضعه أولًا ودون تأكيد، كما يجلس إلغاء بجوار حذف.

القائمة المنسدلة تتجاهله. قائمة لا تُغلق بالنقر خارجها تكون قد حبست الصفحة، والقائمة المنسدلة ليست أبدًا طريقة طرح سؤال.

القائمة المنسدلة تفتح بجانب ما نفّذ المعالج. لا شيء تختاره: ضع openDropdown("بطاقة القائمة") على الزر، فتظهر القائمة بجانب ذلك الزر.

أما أين منه فيمكنك تحديده — ‎openDropdown("بطاقة القائمة", "bottom-end")‎:

الموضعأين تظهر البطاقة
‎"bottom"‎ (الافتراضي)تحت الزر، في وسطه
‎"bottom-start"‎ / ‎"bottom-end"‎تحته، محاذية لحافة البداية / النهاية
‎"top"‎ و‎"top-start"‎ و‎"top-end"‎فوقه — لعنصر قريب من أسفل الشاشة

end وليس right. البطاقة المفتوحة بـ bottom-end تتدلى من الحافة اليمنى للزر في الإنجليزية ومن حافته اليسرى في الفارسية والعربية، وهي القاعدة نفسها التي تتبعها اللوحات الجانبية: نصّ واحد، والقائمة في الجهة الصحيحة بكل لغة. وكتابة ‎"bottom-right"‎ تعني شاشة معكوسة في العربية دون ما يشير إلى ذلك — فهي ليست موضعًا، والمعالج الذي يطلبها يُخبَر.

وهذا تفضيل لا وعد: إن لم يكن تحته متسع فُتحت القائمة فوقه. قائمة تخرج من الشاشة أسوأ من قائمة فُتحت في الجهة الأخرى.

عرضها هو العرض الذي رسمتها به. عرض البطاقة نفسها هو عرض اللوحة الجانبية وعرض النافذة — فارسم البطاقة بالحجم الذي تريده للوحة.

من كل نوع واحدة، وعرض بطاقة ثانية يحلّ محلّ الأولى. لا تفتح الشاشة أكثر من نافذة واحدة في وقت واحد، ولهذا لا شيء يُعلَن: المضيفات موجودة دائمًا، ومعالجك يقول ما الذي يدخل إحداها.

حافّة اللوحة الجانبية تتبع اللغة لا الرسم. البداية هي اليسار لقارئ الإنجليزية واليمين لقارئ العربية أو الفارسية — تنعكس الشاشة كلها، لأن ما رسمته في التطبيق هو الإطار نفسه.

Esc والخلفية والنقر خارجها تغلق الطبقة جميعًا، ويعود التركيز إلى ما فتحها. ولا شيء من ذلك تضبطه أنت.

الخطوة ٦ — عاين​

اضغط معاينة. المعاينة هي زمن التشغيل الحقيقي — المكوّن نفسه الذي تركّبه القشرة، لا تقريبٌ له — فما تراه هو ما يراه مستخدم التطبيق.

اكتب في الصناديق، وراقب بقيّة الشاشة وهي تتبع.

قواعد يُحسن معرفتها​

الارتباط يقرأ فقط. يُنفَّذ من جديد كلما تغيّر ما يقرأه، فالارتباط الذي يغيّر شيئًا يُعيد تشغيل نفسه بلا نهاية. يرفض المصمّم ذلك عند الحفظ ويسمّي الكائن والخاصية — وكل ما يغيّر شيئًا مكانه معالج.

«الظهور» يحذف ولا يخفي. العنصر الذي ظهوره خطأ ليس على الصفحة إطلاقًا: لا ينتقل إليه Tab، ولا يعلنه قارئ الشاشة، ولا يجده Ctrl+F. وهذا هو المقصود في الغالب.

لا شيء هنا أمان. الزرّ المخفيّ مخفيّ فحسب. أما ما يُسمح لشخص بفعله حقًا فيقرّره الخادم في كل مرة، مهما عرضت الشاشة.

ما يراه الارتباط اليوم​

الاسمما هو
stateحالة الشاشة نفسها، من الخطوة ١
screenscreen.width وscreen.height — السطح، لتتفاعل الشاشة مع نافذة ضيّقة — وscreen.name، الشاشة التي أنت عليها، ليدلّ شريط موضوع على كل شاشة على صفحته
uiui.theme (light أو dark)، وui.locale (en، fa، ar)، وui.dir (ltr أو rtl) — إعدادات القارئ نفسه
userمن سجّل الدخول: user.name وuser.email وuser.photo (فارغ إن لم تكن له صورة، فاربط Shown الصورة بـuser.photo != "") — يكفي لترحيب أو شريط علوي، ولا يحمل أدواره عمدًا
screen.paramsما سلّمته إليك الشاشة التي أرسلتك — navigate("Details", { id })

السجلات تصل عبر معالج، لا عبر اسم خاص بها. يضعها loadRecords وgetRecord في حالة الشاشة نفسها، فيقرأ الارتباط state.tickets كأي شيء آخر — ولا مكان ثانيًا للبحث فيه. والذي ليس هنا فعلًا هو العروض المحفوظة في المنصّة: الشاشة تسمّي جدولًا وwhere، لا عرضًا.

ما يمكن للارتباط حسابه​

الارتباط تعبير، فلا يمكنه أن يكرّر ولا أن يستدعي setState. أربع دوال صغيرة تغطي ما يحتاجه العنوان عادةً وراء الحساب:

الدالةتجيب
text(value)القيمة كلماتٍ، ليدخل رقم في جملة — "of " + text(state.total)
num(value)الكلمات رقمًا — ما تعيده القائمة المنسدلة، جاهزًا للحساب؛ 0 إن لم يكن رقمًا
first(list, { field: value, … })أول صف في قائمة محمّلة تحمل حقوله تلك القيم، أو null — first(state.budgets, { par_department: state.department, par_category: state.category })
format(value, "integer" | "number" | "percent")رقم كما تكتبه لغتك — format(174500, "integer") هو 174,500 بالإنجليزية وبأرقام فارسية بالفارسية
translate({ en: "…", fa: "…", ar: "…" })الكلمات بلغة القارئ، وإلا فالإنجليزية — انظر لغتان

first هي الطريقة التي يتبع بها العنوان قائمةً منسدلة إلى الصف الذي تسمّيه. حمّل الجدول مرة واحدة، عند فُتحت الشاشة؛ ثم "Available: £" + format(first(state.budgets, { … }).par_remainingbudget, "integer") يتبع قائمتين منسدلتين أينما ذهبتا، بلا معالج. الصف المحمّل يحمل عمود الاختيار بتسميته، والقائمة المنسدلة تعيد التسمية التي عرضتها، فيتفق الاثنان. احرس الحالة التي لا يطابق فيها شيء بشرط — first(…) == null ? "Choose a line" : … — لأن قراءة حقل من null تُفشل الارتباط، و&& لا يحرسه: طرفا && يُقيَّمان دائمًا.

النطاق عمل المعالج. first تطابق القيم تمامًا؛ «الشريحة التي يحصر حدّاها هذا المبلغ» حلقة، والحلقة مكانها معالج تغيّرت القيمة في الأداة نفسها، يكتب ما وجده إلى الحالة ليقرأه العنوان. نموذج studio-capital-planning في samples/solutions يفعل الأمرين على شاشة واحدة.

لغتان على شاشة واحدة​

اربط نص العنصر النصي بـtranslate(...)، وكل لغة بجانب الإنجليزية:

translate({ en: "Opening times", ar: "مواعيد العمل" })

وأعطِ الصفحة زرًا يبدّل اللغة: if (ui.locale == "ar") { setLanguage("en"); } else { setLanguage("ar"); }. setLanguage يغيّر لغة المنصّة لا لغة الشاشة وحدها: تُحفظ، وبالعربية أو الفارسية تنعكس الصفحة كلها، لأن كل موضع ترسمه الشاشة بداية ونهاية لا يسار ويمين. وui.dir يقول في أي اتجاه تُقرأ، ليشير السهم إلى الجهة الصحيحة.

اللغة التي لا يملكها الكائن تعود إلى الإنجليزية، فالصفحة المترجمة جزئيًا تُقرأ بالإنجليزية لا فارغة. نموذج portal-pages-and-navigation مكتوب هكذا، بكل كلمة فيه.

شيء يحدث وحده: المؤقِّت​

أعطِ عنصرًا معالج عند كل نبضة للمؤقِّت واربط فاصل المؤقِّت (ملّي ثانية) — فيشغّل ذلك المعالج كل بضع ملّي ثوانٍ ما دام العنصر على الصفحة:

فاصل المؤقِّت (ملّي ثانية): state.held ? 0 : 5000
عند كل نبضة للمؤقِّت: setState("slide", (getState("slide") + 1) % 3);

الصفر، أو لا شيء، يعني التوقّف — فالإيقاف المؤقت جزء من الربط نفسه، والشريط الدوّار الذي يتوقف حين يشير إليه المؤشّر يضبط held في معالجَي عند وصول المؤشّر وعند مغادرة المؤشّر. ما دون ٢٥٠ يُرفع إلى ٢٥٠. والعنصر المخفي لا ينبض. الشريط الدوّار في مكتبة المكوّنات، وفي الصفحة الرئيسية لنموذج portal-pages-and-navigation، مبنيّ هكذا.

حل المشكلات​

  • «تعذّر تنفيذ ارتباط على …» — التعبير صحيح لكن شيئًا يسمّيه غير موجود. أكثر الأسباب شيوعًا حقل حالة مكتوب بهجاء مختلف عن الارتباط: راجع قائمة حالة الشاشة على إطار الشاشة.
  • لا يدعني أكتب في الصندوق. — قيمته تعبير محسوب، فهو للقراءة فقط عن قصد. اربطه بحقل حالة بسيط واحسب الشيء الآخر في مكان آخر.
  • لا يحدث شيء عند الكتابة. — الصندوق بلا ارتباط قيمة. الصندوق غير المرتبط يحتفظ بما يُكتب فيه ولا يخبر أحدًا.
  • زر الحفظ لا يعمل أبدًا. — اختبر تعبير مفعّل مع قيمة فارغة: state.requester != "" لا يصحّ إلا بعد كتابة شيء.
  • الضغط على الزر لا يفعل شيئًا. — الأداة المعطّلة لا تتلقّى ضغطًا أصلًا، فابدأ بارتباط مفعّل؛ ثم تأكّد أن المعالج على عند الضغط لا على حدث لا يقع هنا («عند فتح الشاشة» لإطار الشاشة، و«عند تغيّر القيمة» لأداة).
  • «تعذّر تنفيذ معالج على …» — المعالج BS Lang صالح لكن شيئًا يسمّيه غير موجود. وأكثر الأسباب شيوعًا setState على حقل لم تعلنه الشاشة.
  • «createRecord("…") failed: …» — رفضت المنصّة الكتابة، وما بعد النقطتين سببها هي: حقل مطلوب تُرك فارغًا، أو قيمة ليست من الخيارات، أو صلاحية لا تملكها. ابدأ بمطابقة أسماء الحقول مع مصمّم الجداول — فالاسم المنطقي المكتوب بخطأ أكثر الأسباب شيوعًا.
  • لا شيء يُحمَّل، ولا شيء يشتكي. — أنت في المعاينة، ولا بيئة خلفها. نداءات السجلات لا تفعل شيئًا هناك، عن قصد. افتح التطبيق من القشرة لترى سجلات حقيقية.
  • showModal("…") لا يفعل شيئًا. — يجب أن يطابق الاسم اسم المكوّن تمامًا، وأن يكون بطاقة تراكب معلَّمًا عليه. والبطاقة المحذوفة أو المعاد تسميتها تترك معالجات تسمّيها، وتلك تصمت بدل أن تكسر الشاشة.
  • البطاقة تظهر على الشاشة بدل النافذة. — بطاقة تراكب غير معلَّم. وحتى تُعلّمه يبقى المكوّن مرسومًا حيث وضعته، كأي مكوّن آخر.
  • «مكوّنان يحملان الاسم …» — تُعرض البطاقة باسم مكوّنها، فاثنان بالاسم نفسه سؤال لا يستطيع showModal الإجابة عنه. غيّر اسم أحدهما.

جرّبه​

استورد حل Ticket Triage النموذجي. إنه هذه الصفحة وقد بُنيت: ثلاثة حقول حالة، وارتباطات في كل مكان، وحالة سمّاها النموذج بنفسه، ونافذة تسأل قبل رفع التذكرة، وقائمة منسدلة تفتح من زرّها.