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

نخستین اپلیکیشن استودیویی خود را بسازید

هدف​

از هیچ، یک اپلیکیشن کارآمد بسازید: جدولی برای نگه‌داشتن رکوردها، صفحه‌ای برای نمایششان، نوار ابزاری که روی رکوردِ تیک‌خورده کار می‌کند، و کشویی که رکورد تازه‌ای می‌نویسد.

بار نخست حدود چهل دقیقه می‌برد. هیچ‌چیز این‌جا اسباب‌بازی نیست — هر گام همان گامی است که برای یک اپلیکیشن واقعی برمی‌دارید، و چیزی که در پایان دارید تقریباً همان نمونهٔ میز پشتیبانی است.

اول نسخهٔ آماده را ببینید

studio-starter-app در samples/solutions همین اپلیکیشن است، کشیده‌شده. از راهکارها ← درون‌ریزی واردش کنید، بازش کنید و دکمه‌هایش را فشار دهید. برگشتن به این صفحه پس از آن، هر گام زیر را کوتاه‌تر می‌کند.

پیش از آغاز​

  • به‌عنوان سازنده وارد شده‌اید.
  • یک محیط و یک راهکار در انتخابگرهای بالای صفحه برگزیده شده‌اند. هیچ‌چیزی که پایین می‌سازید بیرون از آن‌ها وجود ندارد.
  • پیشوند ناشر راهکارتان را می‌دانید — دو یا سه حرفی که پیش از هر نام منطقی می‌آید. این صفحه par می‌نویسد؛ مالِ شما ممکن است فرق کند.

۱. جدولی برای نگه‌داشتن رکوردها​

اپلیکیشنی که چیزی نشان نمی‌دهد یک نقاشی است. اول جدول را بسازید.

جدول‌ها ← جدید، و پر کنید:

میدانمقدار
نام نمایشیCase
نام جمعCases
نام منطقیpar_case (پیشوند خودکار افزوده می‌شود)
میدان اصلیSummary

ذخیره کنید، سپس در زبانهٔ میدان‌ها سه میدان دیگر بیفزایید:

نام نمایشینام منطقینوع
Prioritypar_priorityگزینه‌ای — مجموعه‌گزینهٔ تازهٔ par_case_priority با Low، Normal، High
Urgentpar_urgentبله/خیر
Notespar_notesمتن چندخطی
میدان بله/خیر از هیچ آغاز می‌شود، نه از خیر

رکوردی که بی‌دست‌زدن به Urgent ساخته شود، هیچ مقداری برایش ندارد — و «هیچ مقدار» بولی نیست. پس عبارتی مثل record.par_urgent ? "Yes" : "No" دقیقاً روی همان رکوردها با پیام Only booleans are allowed in this expression شکست می‌خورد. هرجا میدانی اختیاری را می‌خوانید (record.par_urgent ?? false) بنویسید تا این پرسش از میان برود. این رایج‌ترین شگفتی در نخستین اپلیکیشن است.


۲. اپلیکیشن​

اپلیکیشن‌های استودیو ← جدید:

میدانمقدار
نام نمایشیSupport desk
نام منطقیpar_support
توضیحیک جمله. همان چیزی است که کاشیِ راه‌انداز زیر نام نشان می‌دهد.
نقشکانتخابگر را فشار دهید و یکی برگزینید — LifeBuoy برای اپلیکیشن پشتیبانی مناسب است
فهرست‌شده در پوستهروشن

ذخیره را بزنید، سپس طراحی. بوم باز می‌شود.


۳. صفحه، و اینکه صفحه چیست​

یک قاب بکشید که بوم را پر کند — حدود ۱۲۰۰ × ۷۶۰ اندازهٔ راحتی برای کشیدن است — و با انتخابش، زبانهٔ صفحه را در بازرسِ سمت راست باز کنید:

┌─ بازرس ───────────────────────────┐
│ گرافیک │ صفحه │ کد │
├───────────────────────────────────┤
│ این شیء … │
│ ┌─────────────────────────────┐ │
│ │ صفحه است ▾ │ │ ← این را برگزینید
│ └─────────────────────────────┘ │
│ │
│ محتوایی که جا نمی‌شود │
│ ┌─────────────────────────────┐ │
│ │ نشان داده شود ▾ │ │
│ └─────────────────────────────┘ │
└───────────────────────────────────┘

قاب را در پنل لایه‌ها Cases نام‌گذاری کنید. آن نام، نامِ صفحه است — همان چیزی که بعدها navigate("Cases") می‌خواهد.

اندازه‌ای که کشیدید اندازهٔ نهایی نیست. هرچه درون آن است به لبه‌های قاب سنجاق می‌شود، و قاب هر پنجره‌ای را که اپلیکیشن در آن باز شود پر می‌کند.


۴. حافظهٔ خودِ صفحه​

صفحه چیزهایی را به یاد می‌سپارد: در کدام صفحه از فهرست هستید، در جعبهٔ جست‌وجو چه تایپ شده، کدام ردیف‌ها تیک خورده‌اند. آن حافظه وضعیت صفحه است و یک‌بار روی قاب صفحه اعلام می‌شود.

با انتخاب قاب صفحه، در زبانهٔ صفحه، این‌ها را زیر وضعیت بیفزایید:

ناممقدار آغازین
cases[]
caseTotal0
page0
pageSize20
picked""
pickedCount0
pickedId""
form{ par_name: "", par_urgent: false, par_notes: "" }

شکل form را اعلام کنید، آن را {} نگذارید. اتصالی که عضوی را بخواند که شیء ندارد خطا می‌دهد، و نخستین ارزیابیِ صفحه پیش از آنکه چیزی فرم را پر کند اجرا می‌شود.


۵. پر کردن فهرست​

همچنان روی قاب صفحه، در زبانهٔ صفحه، رویدادگردان‌ها را بیابید و این را در بارگذاری‌شده بگذارید:

loadRecords("par_case", "cases", {
top: getState("pageSize"),
skip: getState("page") * getState("pageSize"),
orderBy: "par_name",
countInto: "caseTotal"
});

یک سطر پرس‌وجو تمامِ صفحه‌بندی است: top و skip صفحه‌اند، و countInto می‌آورد که در کل چند ردیف هست — چیزی که یک صفحهٔ بیست‌تایی نمی‌تواند بگوید و دکمهٔ بعدی باید بداند. باقی‌اش در نمایش یک فهرست، صفحه‌به‌صفحه است.

هنوز چیزی دیده نمی‌شود، چون چیزی کشیده نشده که نشانش دهد.


۶. یک ردیف، یک‌بار کشیده​

این گام مردم را غافلگیر می‌کند، پس روشن بگوییم: شما یک ردیف می‌کشید و سکو برای هر رکورد یکی می‌کشد.

۱. درون صفحه، قابی برای فهرست بکشید — تمام‌پهنای محتوا و چند صد پیکسل بلندی. نامش را Rows بگذارید. در زبانهٔ صفحه، این شیء ← فهرست است و محتوایی که جا نمی‌شود ← با پیمایش در دسترس باشد، سپس جهت پیمایش ← عمودی. جعبه‌ای که برای شش ردیف کشیده شده، با یک جدول واقعی صد ردیف خواهد داشت. ۲. در زبانهٔ گرافیک به آن چیدمان خودکارِ عمودی بدهید. بی آن، هر رونوشتِ ردیف روی نخستین می‌افتد. ۳. یک قاب درون Rows بکشید، تمام‌پهنا و ۶۴ بلند. نامش Case row و این شیء ← قلم فهرست است. ۴. با انتخاب Case row، در زبانهٔ صفحه، تکرار برای هر را بیابید و بنویسید:

state.cases

۵. درون ردیف، متنی برای خلاصه بکشید. انتخابش کنید و در اتصال‌ها، متن را چنین بگذارید:

row.par_name

row رکوردِ همین رونوشت است. ذخیره کنید و پیش‌نمایش را بزنید: یک ردیف به‌ازای هر پرونده، از جدول، بی‌آنکه چیزی دستی تکرار شده باشد.

┌─ Rows ────────────────────────────────────────────────┐
│ ☐ ۱ Sample case 1 Normal │ ← یک‌بار کشیده‌شده
│ ☐ ۲ Sample case 2 High Urgent │
│ ☐ ۳ Sample case 3 Low │
│ … │
└───────────────────────────────────────────────────────┘

۷. تیک، و یک تصور از آنچه برگزیده شده​

در آغاز ردیف قابی کوچک ۲۰×۲۰ بکشید و کنترل ← جعبهٔ تیک بگذارید. سپس:

  • اتصال‌ها ← مقدار: state.picked.includes(row.id)

  • رویدادگردان‌ها ← تغییر کرد:

    var ids = "";
    var count = 0;
    var firstId = "";
    var i = 0;
    for (i = 0; i < state.cases.length(); i = i + 1) {
    var id = state.cases[i].id;
    var on = state.picked.includes(id);
    if (id == row.id) { on = !on; }
    if (on) {
    ids = ids + id + "|";
    count = count + 1;
    if (count == 1) { firstId = id; }
    }
    }
    setState("picked", ids);
    setState("pickedCount", count);
    setState("pickedId", firstId);

مقدار یک مقایسه است، نه یک میدان. هر تیک همان یک فهرست را دوباره می‌خواند، پس پاک‌کردن فهرست همه را یک‌جا از تیک می‌اندازد — کاری که پرچمی به‌ازای هر ردیف هرگز نمی‌توانست بکند، چون هیچ‌چیز نمی‌دانست ردیف‌های دیگر کجایند.


۸. نوار ابزاری که بیدار می‌شود​

بالای فهرست نواری بکشید — قابی تمام‌پهنا و ۵۶ بلند، این شیء ← ناحیه است — و سه دکمه در آن بگذارید. برای هرکدام: قابی بکشید، کنترل ← دکمه بگذارید و درونش متنی برای عنوان بکشید.

روی هرکدام نقشکی بگذارید. دکمه را برگزینید و از انتخابگر نقشک در زبانهٔ گرافیک استفاده کنید — Plus، Pencil، Trash2. آنچه فرود می‌آید مسیری کشیده‌شده درون دکمه است، و دکمهٔ نهایی خودش نقشک و فاصله و عنوان را می‌چیند.

دکمهاتصال‌ها ← فعالرویدادگردان‌ها ← کلیک
New(خالی)setState("form", { par_name: "", par_urgent: false, par_notes: "" });
openEndDrawer("New case");
Editstate.pickedCount == 1openStartDrawer("Edit case");
Deletestate.pickedCount > 0showModal("Confirm delete");

به‌جای نوشتن کد برای خاکستری‌کردن، فعال را متصل کنید. اتصال با هر تغییرِ وضعیت دوباره ارزیابی می‌شود، پس نوار خودبه‌خود بیدار می‌شود و می‌خوابد.

متن و کادر دکمهٔ Delete را با توکن color.error رنگ کنید، نه با قرمزی که خودتان برداشته‌اید. توکن از حالت رنگیِ خواننده پیروی می‌کند؛ کد شانزده‌شانزدهی برای کسی که کشیده درست است و برای هرکس که پوسته‌اش فرق دارد نادرست.


۹. کشو، و اینکه کارت رویه چیست​

رویه یک صفحه نیست. مؤلفه‌ای است که آن را کارت تیک می‌زنید، و رویدادگردان تصمیم می‌گیرد کجا باز شود.

۱. فرم را جایی دور از صفحه بکشید — قابی حدود ۴۲۰ پهنا، با عنوان، میدان‌ها و دو دکمه. جای آن روی بوم مهم نیست؛ کارت در مبدأ ترجمه می‌شود و تنها پهنایش نگه داشته می‌شود. ۲. قاب را برگزینید، ساختن مؤلفه را بزنید و نامش را New case بگذارید. ۳. در پنل مؤلفه، کارت رویه را تیک بزنید.

┌─ بازرس · مؤلفه ───────────────────┐
│ ☑ کارت رویه │
│ با showModal("New case") … │
│ پهنای کشیده‌شده‌اش پهنای پنل است.│
│ │
│ ☐ باید پاسخ داده شود │
│ تا وقتی باز است، کلیک بیرون، │
│ کلید Escape و دکمهٔ بستن کار │
│ نمی‌کنند. │
└───────────────────────────────────┘

درون آن، برای هر میدان: قابی بکشید، کنترل ← جعبهٔ متن (یا ناحیهٔ متن برای یادداشت‌ها) بگذارید و مقدار را به مسیر وضعیتی که ویرایش می‌کند متصل کنید — state.form.par_name، state.form.par_notes. کنترلی که به یک مسیر سادهٔ وضعیت متصل است در همان‌جا بازمی‌نویسد، پس چیز دیگری برای سیم‌کشی نیست.

سپس کلیک دکمهٔ ذخیره:

if (getState("form").par_name == "") {
notifyError("A case needs a summary.");
} else {
createRecord("par_case", {
par_name: getState("form").par_name,
par_urgent: getState("form").par_urgent,
par_notes: getState("form").par_notes
});
notify("Case created.");
closeEndDrawer();
setState("page", 0);
loadRecords("par_case", "cases", { top: getState("pageSize"), skip: 0,
orderBy: "par_name", countInto: "caseTotal" });
}

هر نوشتن با خواندنِ دوبارهٔ فهرست پایان می‌یابد. هیچ‌چیز خودبه‌خود تازه نمی‌شود — صفحه آنچه را وضعیتش دارد نشان می‌دهد، و وضعیت آن چیزی را دارد که آخرین پرس‌وجو گذاشته است.

پرسشی که باید پاسخ بگیرد​

تأییدِ حذف را به همان شیوه بکشید، کارت رویه را تیک بزنید و سپس باید پاسخ داده شود را هم تیک بزنید. کلیک بیرون از آن، کلید Escape و دکمهٔ بستن همگی از کار می‌افتند — پس راه خروج خودتان را بکشید: دکمهٔ Cancel که closeModal() را اجرا کند.

این تیک دوم را جای دیگری روشن نگذارید. رویه‌ای که کسی تصادفی باز کرده باید به همان راهی که باز شد بسته شود؛ تنها آن یکی باید سرباز بزند که کلیک در کنارش پاسخ نیست.


۱۰. ذخیره، و باز کردنش به‌طور واقعی​

ذخیره را بزنید. اگر مترجم چیزی دیده باشد، هشدارها بالای صفحه می‌آیند — دکمه‌ای بی‌واژه، قاب صفحه‌ای که نیست، اتصالی که عبارت ناب نیست. هشدار شیء را نام می‌برد، پس با انتخابش درستش کنید.

سپس طراح را ترک کنید و اپلیکیشن را از فهرست اپلیکیشن‌های پوسته باز کنید. پیش‌نمایش همان نیست: همان صفحه‌ها را بی‌هیچ محیطی پشتشان رسم می‌کند، پس آنچه نمی‌تواند نشانتان دهد داده‌هایتان است. باقیِ چیزها — کشوها، جعبه‌های تیک، نوار ابزار — دقیقاً یکسان رفتار می‌کنند.


گام بعد​

  • صفحهٔ دوم. قاب دیگری کنار نخستین بکشید، صفحه برچسبش بزنید و نامش را بگذارید. به‌محض وجود دو صفحه، انتخابگر باز می‌شود در در نوار عنوان پیدا می‌شود. navigate("Board") میان آن‌ها جابه‌جا می‌کند.
  • زبانه‌ها. نوار زبانه کنترل نیست: یک میدان وضعیت است و یک پنل برای هر مقدار. پنل‌ها را در یک مستطیل بکشید، نمایان هرکدام را به state.tab == "notes" متصل کنید و به دکمه‌های زبانه اتصال برگزیده با همان مقایسه بدهید. برای رسیدن به پنلی که زیر بقیه است، تنها روی این یکی بکش را در ردیف لایه به کار ببرید.
  • کنترل‌هایی که نکشیده‌اید. نمونهٔ studio-component-library یک کشویی، یک جست‌وجوگر و یک کلید دارد. در آن اپلیکیشن انتشار مؤلفه‌ها را تیک بزنید تا در پالت شما پیدا شوند — ببینید هم‌رسانی یک مؤلفه میان صفحه‌ها.
  • داشبورد. همین بوم یکی می‌کشد: ببینید داشبورد روی یک صفحه.

عیب‌یابی​

آنچه می‌بینیدچیست
Only booleans are allowed in this expressionمیدانی اختیاری به‌عنوان شرط خوانده شده. بپیچیدش: (state.x.par_urgent ?? false).
اتصال یک ردیف خالی خوانده می‌شودrow تنها درون عنصری که تکرار برای هر دارد و فرزندانش وجود دارد.
ردیف‌ها روی هم می‌افتندجعبهٔ گرد ردیفِ تکرارشونده به چیدمان خودکار نیاز دارد.
متن از پایین جعبه بیرون می‌زنددر قابی با با پیمایش در دسترس باشد ← عمودی بگذاریدش و واژه‌ها را به بالا لنگر بزنید.
کشویی، کشویی را که از آن باز شده می‌بندددر ۰٫۳۱ درست شد. به‌روزرسانی کنید، یا انتخابگر را از کشو بیرون ببرید.
ذخیره غیرفعال استاز آخرین ذخیره چیزی تغییر نکرده.