پرش به مطلب اصلی

کاری کنید صفحه واکنش نشان دهد

هدف​

کاری کنید صفحهٔ کشیده‌شده کاری انجام دهد: عنوانی که نامِ تایپ‌شده را می‌گوید، پنلی که با تیک‌خوردن یک کادر ظاهر می‌شود، و دکمهٔ ذخیره‌ای که تنها وقتی فرم پر شده باشد روشن می‌شود.

هیچ‌کدام از این‌ها نقاشی نیست و هیچ‌کدام کدی نیست که در یک فایل بنویسید. این‌ها وضعیت است — همان چند چیزی که هنگام کارِ کاربر با صفحه تغییر می‌کند — و اتصال‌ها، که عبارت‌های یک‌خطی‌ای هستند که آن را می‌خوانند.

گام ۱ — به صفحه وضعیت بدهید​

  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", fa: "ساعات کار" })

و به صفحه دکمه‌ای بدهید که زبان را جابه‌جا کند: if (ui.locale == "fa") { setLanguage("en"); } else { setLanguage("fa"); }. 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 را وارد کنید. همین صفحه است، ساخته‌شده: سه فیلد وضعیت، اتصال‌ها در سراسر صفحه، حالتی که خودِ نمونه نامش را گذاشته، مودالی که پیش از ثبت می‌پرسد، و فهرست کشویی‌ای که از دکمهٔ خودش باز می‌شود.