صفوف مخزّنة، ونموذج يضيف صفًا، وإجراءات صف تُكمل وتحذف، وقائمة تُعاد تحميلها بعد كل كتابة.
يبني هذا الدرس مدير مهام بسمتين في صفحة واحدة: قائمة ونموذج. تُخزَّن المهام في المتصفح، فلا يحتاج التطبيق إلى خادم.
1entity Task [persistent: tasks, local] {
2 id : string!
3 title : string!
4 done : boolean = false
5 instances [
6 { id: "T-1", title: "Sketch the states", done: true },
7 { id: "T-2", title: "Write the transitions", done: false }
8 ]
9}يُبقي local الصفوف في متصفح الزائر، ويعبّئها instances في أول مرة يُنشأ فيها المخزن.
تبدأ القائمة في loading، وتجلب البيانات عند INIT، وتنتقل إلى ready حين تصل الصفوف. يبلّغ الجلب عبر حدثين تعلنهما السمة في emits:
1state loading {
2 INIT -> loading
3 (fetch Task { emit: { success: TasksLoaded, failure: TasksFailed } })
4 (render-ui main { type: skeleton, variant: list, rows: 3 })
5 TasksLoaded -> ready
6 (render-ui main { type: data-list, entity: ?data, fields: [{ name: "title", label: "Task", variant: h4 }] })
7 TasksFailed -> ready
8 (render-ui main { type: alert, variant: error, message: ?error })
9}تضع itemActions أزرارًا على كل صف. يرسل إجراء الصف حدثه مع { id, row }، ويقرر when لكل صف ما إذا كان الزر يظهر:
▼itemActions: [
{ label: "Complete", event: COMPLETE, variant: ghost, when: (fn row (not (object/get @row done false))) },
{ label: "Delete", event: DELETE, variant: danger }
]1COMPLETE -> loading
2 (persist update Task (object/merge ?row { done: true }))
3 (fetch Task { emit: { success: TasksLoaded, failure: TasksFailed } })
4DELETE -> loading
5 (persist delete Task ?id)
6 (fetch Task { emit: { success: TasksLoaded, failure: TasksFailed } })تعرض سمة ثانية form-section. الإرسال يبعث SAVE مع قيم النموذج في ?data، فتخزّن السمة الصف وتبث TASK_ADDED:
SAVE -> editing
(persist create Task ?data)
(emit TASK_ADDED)تستمع القائمة وتعيد التحميل:
▼listens {
TaskForm.TASK_ADDED -> RELOAD
}TaskList
on Task · states: 2 · transitions: 6TaskForm
on Task · states: 1 · transitions: 2البرنامج أعلاه كآلات حالات، واحدة لكل سمة: كل مربع حالة، وكل سهم حدث ينقلها، والأيقونات على السهم هي التأثيرات التي يشغّلها ذلك الحدث. الرمز ◇ على السهم شرط حارس؛ مرّر المؤشر فوقه لتقرأ الشرط. كيف تقرأ AVL
1app std-orb-docex-tasks "1.0.0"
2"std-orb-docex-tasks — a task manager: stored tasks, a form that adds one, row actions that complete or delete, and a list that reloads after every write."
3orbital TasksOrbital {
4 entity Task [persistent: docex_tasks, local] {
5 id : string!
6 title : string!
7 done : boolean = false
8 instances [
9 { id: "T-1", title: "Sketch the states", done: true },
10 { id: "T-2", title: "Write the transitions", done: false },
11 { id: "T-3", title: "Run orb validate", done: false }
12 ]
13 }
14
15 trait TaskList -> Task [interaction, collection] {
16 initial: loading
17
18 state loading {
19 INIT -> loading
20 (fetch Task { emit: { success: TasksLoaded, failure: TasksFailed } })
21 (render-ui main { type: skeleton, variant: list, rows: 3 })
22 TasksLoaded -> ready
23 (emit MOVED { event: "TasksLoaded" })
24 (render-ui main { type: data-list, entity: ?data, fields: [{ name: "title", label: "Task", variant: h4 }, { name: "done", label: "Done", variant: badge, labels: { "true": "Done", "false": "Open" } }], itemActions: [{ label: "Complete", event: COMPLETE, variant: ghost, when: (fn row (not (object/get @row done false))) }, { label: "Delete", event: DELETE, variant: danger }], emptyTitle: "Nothing to do", emptyDescription: "Add a task with the form.", emptyIcon: "check-circle" })
25 TasksFailed -> ready
26 (emit MOVED { event: "TasksFailed" })
27 (render-ui main { type: alert, variant: error, message: ?error })
28 }
29
30 state ready {
31 COMPLETE -> loading
32 (emit MOVED { event: "COMPLETE" })
33 (persist update Task (object/merge ?row { done: true }))
34 (fetch Task { emit: { success: TasksLoaded, failure: TasksFailed } })
35 DELETE -> loading
36 (emit MOVED { event: "DELETE" })
37 (persist delete Task ?id)
38 (fetch Task { emit: { success: TasksLoaded, failure: TasksFailed } })
39 RELOAD -> loading
40 (emit MOVED { event: "RELOAD" })
41 (fetch Task { emit: { success: TasksLoaded, failure: TasksFailed } })
42 }
43
44 emits {
45 TasksLoaded {
46 data : [Task]
47 }
48 TasksFailed {
49 error : string
50 }
51 MOVED -> external {
52 event : string!
53 }
54 @description "Each move names the event it took, so the diagram beside the demo can follow it."
55 }
56
57 listens {
58 TaskForm.TASK_ADDED -> RELOAD
59 COMPLETE {
60 id : Task.id!
61 row : Task!
62 }
63 DELETE {
64 id : Task.id!
65 }
66 }
67 }
68
69 trait TaskForm -> Task [interaction, instance] {
70 initial: editing
71
72 state editing {
73 INIT -> editing
74 (render-ui main { type: form-section, fields: ["title"], submitEvent: SAVE, mode: create, entity: {} })
75 SAVE -> editing
76 (emit MOVED { event: "SAVE" })
77 (persist create Task ?data)
78 (emit TASK_ADDED)
79 (render-ui main { type: form-section, fields: ["title"], submitEvent: SAVE, mode: create, entity: {} })
80 }
81
82 emits {
83 TASK_ADDED
84 @description "A task was stored."
85 MOVED -> external {
86 event : string!
87 }
88 @description "Each move names the event it took, so the diagram beside the demo can follow it."
89 }
90
91 listens {
92 SAVE {
93 data : @entity!
94 }
95 }
96 }
97
98 page "/tasks" -> TaskList, TaskForm
99}ما الذي يحدث عندما يقع الحدث COMPLETE:
(persist "update" "Task" (object/merge @payload.row { done: true }))(fetch "Task" { emit: { failure: "TasksFailed", success: "TasksLoaded" } })كيف تتحادث السمات فيما بينها، بالترتيب:
أضف مهمة، وأكمل واحدة، واحذف أخرى. أعد تحميل الصفحة: المهام ما زالت هناك لأنها مخزّنة في متصفحك.
يتعمق درس أنماط الواجهة في ما تستطيع الشاشة عرضه.