@fui-org/fui-cli 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/README.md +13 -1
  2. package/dist/fui.js +160 -43
  3. package/package.json +4 -2
  4. package/skills/fui/SKILL.md +131 -66
  5. package/skills/fui-skill/README.md +112 -0
  6. package/skills/fui-skill/SKILL.md +269 -0
  7. package/skills/fui-skill/assets/projectdefaultstyle.css +518 -0
  8. package/skills/fui-skill/design-md/airbnb/DESIGN.md +545 -0
  9. package/skills/fui-skill/design-md/airbnb/README.md +5 -0
  10. package/skills/fui-skill/design-md/airtable/DESIGN.md +554 -0
  11. package/skills/fui-skill/design-md/airtable/README.md +5 -0
  12. package/skills/fui-skill/design-md/apple/DESIGN.md +562 -0
  13. package/skills/fui-skill/design-md/apple/README.md +5 -0
  14. package/skills/fui-skill/design-md/asu/DESIGN.md +179 -0
  15. package/skills/fui-skill/design-md/asu/README.md +107 -0
  16. package/skills/fui-skill/design-md/binance/DESIGN.md +634 -0
  17. package/skills/fui-skill/design-md/binance/README.md +5 -0
  18. package/skills/fui-skill/design-md/bmw/DESIGN.md +544 -0
  19. package/skills/fui-skill/design-md/bmw/README.md +5 -0
  20. package/skills/fui-skill/design-md/bmw-m/DESIGN.md +503 -0
  21. package/skills/fui-skill/design-md/bmw-m/README.md +5 -0
  22. package/skills/fui-skill/design-md/bugatti/DESIGN.md +454 -0
  23. package/skills/fui-skill/design-md/bugatti/README.md +5 -0
  24. package/skills/fui-skill/design-md/cal/DESIGN.md +542 -0
  25. package/skills/fui-skill/design-md/cal/README.md +5 -0
  26. package/skills/fui-skill/design-md/claude/DESIGN.md +589 -0
  27. package/skills/fui-skill/design-md/claude/README.md +5 -0
  28. package/skills/fui-skill/design-md/clay/DESIGN.md +541 -0
  29. package/skills/fui-skill/design-md/clay/README.md +5 -0
  30. package/skills/fui-skill/design-md/clickhouse/DESIGN.md +544 -0
  31. package/skills/fui-skill/design-md/clickhouse/README.md +5 -0
  32. package/skills/fui-skill/design-md/cohere/DESIGN.md +451 -0
  33. package/skills/fui-skill/design-md/cohere/README.md +5 -0
  34. package/skills/fui-skill/design-md/coinbase/DESIGN.md +570 -0
  35. package/skills/fui-skill/design-md/coinbase/README.md +5 -0
  36. package/skills/fui-skill/design-md/composio/DESIGN.md +506 -0
  37. package/skills/fui-skill/design-md/composio/README.md +5 -0
  38. package/skills/fui-skill/design-md/cursor/DESIGN.md +537 -0
  39. package/skills/fui-skill/design-md/cursor/README.md +5 -0
  40. package/skills/fui-skill/design-md/elevenlabs/DESIGN.md +504 -0
  41. package/skills/fui-skill/design-md/elevenlabs/README.md +5 -0
  42. package/skills/fui-skill/design-md/expo/DESIGN.md +526 -0
  43. package/skills/fui-skill/design-md/expo/README.md +5 -0
  44. package/skills/fui-skill/design-md/ferrari/DESIGN.md +531 -0
  45. package/skills/fui-skill/design-md/ferrari/README.md +5 -0
  46. package/skills/fui-skill/design-md/figma/DESIGN.md +578 -0
  47. package/skills/fui-skill/design-md/figma/README.md +5 -0
  48. package/skills/fui-skill/design-md/framer/DESIGN.md +544 -0
  49. package/skills/fui-skill/design-md/framer/README.md +5 -0
  50. package/skills/fui-skill/design-md/fui/DESIGN.md +532 -0
  51. package/skills/fui-skill/design-md/hashicorp/DESIGN.md +575 -0
  52. package/skills/fui-skill/design-md/hashicorp/README.md +5 -0
  53. package/skills/fui-skill/design-md/ibm/DESIGN.md +550 -0
  54. package/skills/fui-skill/design-md/ibm/README.md +5 -0
  55. package/skills/fui-skill/design-md/intercom/DESIGN.md +546 -0
  56. package/skills/fui-skill/design-md/intercom/README.md +5 -0
  57. package/skills/fui-skill/design-md/kraken/DESIGN.md +125 -0
  58. package/skills/fui-skill/design-md/kraken/README.md +5 -0
  59. package/skills/fui-skill/design-md/lamborghini/DESIGN.md +288 -0
  60. package/skills/fui-skill/design-md/lamborghini/README.md +5 -0
  61. package/skills/fui-skill/design-md/linear.app/DESIGN.md +548 -0
  62. package/skills/fui-skill/design-md/linear.app/README.md +5 -0
  63. package/skills/fui-skill/design-md/lovable/DESIGN.md +298 -0
  64. package/skills/fui-skill/design-md/lovable/README.md +5 -0
  65. package/skills/fui-skill/design-md/mastercard/DESIGN.md +365 -0
  66. package/skills/fui-skill/design-md/mastercard/README.md +5 -0
  67. package/skills/fui-skill/design-md/meta/DESIGN.md +683 -0
  68. package/skills/fui-skill/design-md/meta/README.md +5 -0
  69. package/skills/fui-skill/design-md/minimax/DESIGN.md +746 -0
  70. package/skills/fui-skill/design-md/minimax/README.md +5 -0
  71. package/skills/fui-skill/design-md/mintlify/DESIGN.md +852 -0
  72. package/skills/fui-skill/design-md/mintlify/README.md +5 -0
  73. package/skills/fui-skill/design-md/miro/DESIGN.md +825 -0
  74. package/skills/fui-skill/design-md/miro/README.md +5 -0
  75. package/skills/fui-skill/design-md/mistral.ai/DESIGN.md +773 -0
  76. package/skills/fui-skill/design-md/mistral.ai/README.md +5 -0
  77. package/skills/fui-skill/design-md/mongodb/DESIGN.md +767 -0
  78. package/skills/fui-skill/design-md/mongodb/README.md +5 -0
  79. package/skills/fui-skill/design-md/nike/DESIGN.md +575 -0
  80. package/skills/fui-skill/design-md/nike/README.md +5 -0
  81. package/skills/fui-skill/design-md/notion/DESIGN.md +821 -0
  82. package/skills/fui-skill/design-md/notion/README.md +5 -0
  83. package/skills/fui-skill/design-md/nvidia/DESIGN.md +640 -0
  84. package/skills/fui-skill/design-md/nvidia/README.md +5 -0
  85. package/skills/fui-skill/design-md/ollama/DESIGN.md +539 -0
  86. package/skills/fui-skill/design-md/ollama/README.md +5 -0
  87. package/skills/fui-skill/design-md/opencode.ai/DESIGN.md +521 -0
  88. package/skills/fui-skill/design-md/opencode.ai/README.md +5 -0
  89. package/skills/fui-skill/design-md/pinterest/DESIGN.md +597 -0
  90. package/skills/fui-skill/design-md/pinterest/README.md +5 -0
  91. package/skills/fui-skill/design-md/playstation/DESIGN.md +661 -0
  92. package/skills/fui-skill/design-md/playstation/README.md +5 -0
  93. package/skills/fui-skill/design-md/posthog/DESIGN.md +690 -0
  94. package/skills/fui-skill/design-md/posthog/README.md +5 -0
  95. package/skills/fui-skill/design-md/raycast/DESIGN.md +669 -0
  96. package/skills/fui-skill/design-md/raycast/README.md +5 -0
  97. package/skills/fui-skill/design-md/renault/DESIGN.md +589 -0
  98. package/skills/fui-skill/design-md/renault/README.md +5 -0
  99. package/skills/fui-skill/design-md/replicate/DESIGN.md +616 -0
  100. package/skills/fui-skill/design-md/replicate/README.md +5 -0
  101. package/skills/fui-skill/design-md/resend/DESIGN.md +585 -0
  102. package/skills/fui-skill/design-md/resend/README.md +5 -0
  103. package/skills/fui-skill/design-md/revolut/DESIGN.md +636 -0
  104. package/skills/fui-skill/design-md/revolut/README.md +5 -0
  105. package/skills/fui-skill/design-md/runwayml/DESIGN.md +244 -0
  106. package/skills/fui-skill/design-md/runwayml/README.md +5 -0
  107. package/skills/fui-skill/design-md/sanity/DESIGN.md +357 -0
  108. package/skills/fui-skill/design-md/sanity/README.md +5 -0
  109. package/skills/fui-skill/design-md/sentry/DESIGN.md +551 -0
  110. package/skills/fui-skill/design-md/sentry/README.md +5 -0
  111. package/skills/fui-skill/design-md/shopify/DESIGN.md +516 -0
  112. package/skills/fui-skill/design-md/shopify/README.md +5 -0
  113. package/skills/fui-skill/design-md/slack/DESIGN.md +482 -0
  114. package/skills/fui-skill/design-md/spacex/DESIGN.md +363 -0
  115. package/skills/fui-skill/design-md/spacex/README.md +5 -0
  116. package/skills/fui-skill/design-md/spotify/DESIGN.md +246 -0
  117. package/skills/fui-skill/design-md/spotify/README.md +5 -0
  118. package/skills/fui-skill/design-md/starbucks/DESIGN.md +580 -0
  119. package/skills/fui-skill/design-md/starbucks/README.md +5 -0
  120. package/skills/fui-skill/design-md/stripe/DESIGN.md +487 -0
  121. package/skills/fui-skill/design-md/stripe/README.md +5 -0
  122. package/skills/fui-skill/design-md/supabase/DESIGN.md +462 -0
  123. package/skills/fui-skill/design-md/supabase/README.md +5 -0
  124. package/skills/fui-skill/design-md/superhuman/DESIGN.md +448 -0
  125. package/skills/fui-skill/design-md/superhuman/README.md +5 -0
  126. package/skills/fui-skill/design-md/tesla/DESIGN.md +286 -0
  127. package/skills/fui-skill/design-md/tesla/README.md +5 -0
  128. package/skills/fui-skill/design-md/theverge/DESIGN.md +339 -0
  129. package/skills/fui-skill/design-md/theverge/README.md +5 -0
  130. package/skills/fui-skill/design-md/together.ai/DESIGN.md +633 -0
  131. package/skills/fui-skill/design-md/together.ai/README.md +5 -0
  132. package/skills/fui-skill/design-md/uber/DESIGN.md +636 -0
  133. package/skills/fui-skill/design-md/uber/README.md +5 -0
  134. package/skills/fui-skill/design-md/vercel/DESIGN.md +736 -0
  135. package/skills/fui-skill/design-md/vercel/README.md +5 -0
  136. package/skills/fui-skill/design-md/vodafone/DESIGN.md +538 -0
  137. package/skills/fui-skill/design-md/vodafone/README.md +5 -0
  138. package/skills/fui-skill/design-md/voltagent/DESIGN.md +521 -0
  139. package/skills/fui-skill/design-md/voltagent/README.md +5 -0
  140. package/skills/fui-skill/design-md/warp/DESIGN.md +526 -0
  141. package/skills/fui-skill/design-md/warp/README.md +5 -0
  142. package/skills/fui-skill/design-md/webflow/DESIGN.md +588 -0
  143. package/skills/fui-skill/design-md/webflow/README.md +5 -0
  144. package/skills/fui-skill/design-md/wired/DESIGN.md +497 -0
  145. package/skills/fui-skill/design-md/wired/README.md +5 -0
  146. package/skills/fui-skill/design-md/wise/DESIGN.md +544 -0
  147. package/skills/fui-skill/design-md/wise/README.md +5 -0
  148. package/skills/fui-skill/design-md/x.ai/DESIGN.md +465 -0
  149. package/skills/fui-skill/design-md/x.ai/README.md +5 -0
  150. package/skills/fui-skill/design-md/zapier/DESIGN.md +537 -0
  151. package/skills/fui-skill/design-md/zapier/README.md +5 -0
  152. package/skills/fui-skill/examples/component.vue +162 -0
  153. package/skills/fui-skill/examples/f-table-patterns.json +331 -0
  154. package/skills/fui-skill/examples/module-patterns.json +973 -0
  155. package/skills/fui-skill/examples/project-patterns.json +222 -0
  156. package/skills/fui-skill/metadata.json +75 -0
  157. package/skills/fui-skill/references/INDEX.md +144 -0
  158. package/skills/fui-skill/references/advanced-techniques.md +160 -0
  159. package/skills/fui-skill/references/coding-standards.md +112 -0
  160. package/skills/fui-skill/references/component-design.md +455 -0
  161. package/skills/fui-skill/references/component-quickref.md +77 -0
  162. package/skills/fui-skill/references/component-table.md +276 -0
  163. package/skills/fui-skill/references/components-dialog.md +192 -0
  164. package/skills/fui-skill/references/components-display.md +147 -0
  165. package/skills/fui-skill/references/components-echart.md +391 -0
  166. package/skills/fui-skill/references/components-input.md +359 -0
  167. package/skills/fui-skill/references/controls-patterns.md +847 -0
  168. package/skills/fui-skill/references/controls-styling-vocabulary.md +140 -0
  169. package/skills/fui-skill/references/db-table-design.md +77 -0
  170. package/skills/fui-skill/references/db-workflow.md +504 -0
  171. package/skills/fui-skill/references/default-function.md +415 -0
  172. package/skills/fui-skill/references/design-modes.md +85 -0
  173. package/skills/fui-skill/references/echart-templates.md +481 -0
  174. package/skills/fui-skill/references/fastproject.md +97 -0
  175. package/skills/fui-skill/references/fsheet.md +218 -0
  176. package/skills/fui-skill/references/fullstack-workflow.md +351 -0
  177. package/skills/fui-skill/references/module-data-patterns.md +126 -0
  178. package/skills/fui-skill/references/module-json-anatomy.md +137 -0
  179. package/skills/fui-skill/references/module-structure.md +260 -0
  180. package/skills/fui-skill/references/new-session.md +108 -0
  181. package/skills/fui-skill/references/pdfmake.md +60 -0
  182. package/skills/fui-skill/references/permission-system.md +169 -0
  183. package/skills/fui-skill/references/platform-architecture.md +294 -0
  184. package/skills/fui-skill/references/project-config.md +335 -0
  185. package/skills/fui-skill/references/project-provisioning.md +383 -0
  186. package/skills/fui-skill/references/script-map.md +296 -0
  187. package/skills/fui-skill/references/sql-clr-functions.md +224 -0
  188. package/skills/fui-skill/references/system-design.md +116 -0
  189. package/skills/fui-skill/references/tapi-file-api.md +191 -0
  190. package/skills/fui-skill/references/tapi-permission-patterns.md +158 -0
  191. package/skills/fui-skill/references/tapi-reference.md +549 -0
  192. package/skills/fui-skill/references/tools-registry.md +460 -0
  193. package/skills/fui-skill/references/ui-crosswindow-patterns.md +317 -0
  194. package/skills/fui-skill/references/ui-dialog-patterns.md +229 -0
  195. package/skills/fui-skill/references/ui-layout-patterns.md +176 -0
  196. package/skills/fui-skill/references/ui-patterns.md +315 -0
  197. package/skills/fui-skill/references/ui-screenshot-review.md +94 -0
  198. package/skills/fui-skill/references/ui-table-cell-patterns.md +316 -0
  199. package/skills/fui-skill/references/ui-templates.md +29 -0
  200. package/skills/fui-skill/references/verification.md +246 -0
  201. package/skills/fui-skill/references/watcher-patterns.md +196 -0
  202. package/skills/fui-skill/references/websocket-realtime.md +254 -0
  203. package/skills/fui-skill/scripts/component-3.0.js +2549 -0
  204. package/skills/fui-skill/scripts/component.js +3142 -0
  205. package/skills/fui-skill/scripts/componentTable-3.0.js +909 -0
  206. package/skills/fui-skill/scripts/componentTable.js +769 -0
  207. package/skills/fui-skill/scripts/defaultfunction-3.0.js +781 -0
  208. package/skills/fui-skill/scripts/defaultfunction.js +966 -0
  209. package/skills/fui-skill/scripts/fastproject-3.0.js +870 -0
  210. package/skills/fui-skill/scripts/fastproject.js +828 -0
  211. package/skills/fui-skill/scripts/fechart.js +890 -0
  212. package/skills/fui-skill/scripts/fsheet.js +1330 -0
@@ -0,0 +1,196 @@
1
+ # Watcher Patterns (FUI Best Practice)
2
+
3
+ > File này sở hữu: **`watch`: `v_old`, deep-watch, cascading, chống race**. Cú pháp action xem [controls-patterns.md](controls-patterns.md).
4
+
5
+ Hướng này dùng cho các case:
6
+ - Form phụ thuộc nhiều cấp (Tỉnh/Thành -> Quận/Huyện -> Phường/Xã)
7
+ - Bộ lọc theo thuộc tính (status, loại, ngày, keyword, ...)
8
+
9
+ Mục tiêu:
10
+ - Đúng cấu trúc FUI (`module.json`: `data`, `watch`, `controls`, `set`)
11
+ - Tránh vòng lặp watcher
12
+ - Giảm gọi API thừa
13
+ - Dễ bảo trì khi module lớn
14
+
15
+ ## 1. Nguyên tắc chuẩn
16
+
17
+ 1. Chỉ watch key đầu vào nhỏ nhất
18
+ - Watch ID hoặc field filter (`provinceID`, `districtID`, `filter.status`), không watch cả object lớn nếu không cần.
19
+
20
+ 2. Tách action theo tầng
21
+ - `handleProvinceChange` chỉ reset dữ liệu phụ thuộc và gọi `apiLoadDistricts`.
22
+ - `handleDistrictChange` chỉ reset cấp dưới và gọi `apiLoadWards`.
23
+ - `applyFilters` gom logic tải list cuối.
24
+
25
+ 3. Dùng `deep-watch` có chọn lọc
26
+ - Chỉ dùng cho object filter nhỏ (ví dụ `filter`).
27
+ - Không deep-watch mảng lớn như `items`, `tableData`.
28
+
29
+ 4. Không để watcher tự tạo loop
30
+ - Không watch biến output của chính action đó.
31
+ - Không watch `items` rồi trong callback lại ghi `items`.
32
+
33
+ 5. Gắn điều kiện trước khi gọi API
34
+ - Dùng `IF/THEN/ELSE` để kiểm tra đầu vào hợp lệ (ví dụ chưa chọn tỉnh thì không gọi huyện).
35
+
36
+ 6. Tách debounce/race-control sang `script.js`
37
+ - Watcher giữ vai trò điều phối.
38
+ - Debounce hoặc chống response cũ ghi đè response mới đặt trong helper JS.
39
+
40
+ 7. Literal string chỉ ép khi truyền qua `IN`
41
+ - Trong action có `IN`, chuỗi không có khoảng trắng có thể bị core hiểu là expression → dùng
42
+ `` `value`` hoặc `"'value'"`. Quy tắc đầy đủ:
43
+ [controls-patterns.md §0](controls-patterns.md#0-value-resolution-rules-fundamental).
44
+
45
+ ## 2. Pattern A: Cascading địa giới hành chính
46
+
47
+ ```json
48
+ {
49
+ "data": [
50
+ {
51
+ "formData": {
52
+ "provinceID": null,
53
+ "districtID": null,
54
+ "wardID": null
55
+ },
56
+ "provinceList": [],
57
+ "districtList": [],
58
+ "wardList": []
59
+ },
60
+ {
61
+ "apiLoadProvinces": {
62
+ "API": "/api/location/provinces",
63
+ "OUT": "provinceList"
64
+ },
65
+ "apiLoadDistricts": {
66
+ "API": "/api/location/districts",
67
+ "IN": { "ProvinceID": "formData.provinceID" },
68
+ "OUT": "districtList"
69
+ },
70
+ "apiLoadWards": {
71
+ "API": "/api/location/wards",
72
+ "IN": { "DistrictID": "formData.districtID" },
73
+ "OUT": "wardList"
74
+ }
75
+ },
76
+ {
77
+ "handleProvinceChange": [
78
+ {
79
+ "districtList": [],
80
+ "wardList": [],
81
+ "formData.districtID": null,
82
+ "formData.wardID": null
83
+ },
84
+ {
85
+ "IF": "formData.provinceID",
86
+ "THEN": { "CALL": "apiLoadDistricts" }
87
+ }
88
+ ],
89
+ "handleDistrictChange": [
90
+ {
91
+ "wardList": [],
92
+ "formData.wardID": null
93
+ },
94
+ {
95
+ "IF": "formData.districtID",
96
+ "THEN": { "CALL": "apiLoadWards" }
97
+ }
98
+ ]
99
+ },
100
+ {
101
+ "CALL": "apiLoadProvinces"
102
+ }
103
+ ],
104
+ "watch": {
105
+ "formData.provinceID": { "CALL": "handleProvinceChange" },
106
+ "formData.districtID": { "CALL": "handleDistrictChange" }
107
+ }
108
+ }
109
+ ```
110
+
111
+ Điểm chính:
112
+ - Watch theo key cụ thể.
113
+ - Reset cấp dưới trước khi gọi API cấp dưới.
114
+ - Có điều kiện tránh gọi API khi null.
115
+
116
+ ## 3. Pattern B: Filter object + deep-watch
117
+
118
+ ```json
119
+ {
120
+ "data": [
121
+ {
122
+ "filter": {
123
+ "keyword": "",
124
+ "status": null,
125
+ "fromDate": null,
126
+ "toDate": null
127
+ },
128
+ "items": []
129
+ },
130
+ {
131
+ "apiLoadItems": {
132
+ "API": "/api/items/search",
133
+ "IN": {
134
+ "Keyword": "filter.keyword",
135
+ "Status": "filter.status",
136
+ "FromDate": "filter.fromDate",
137
+ "ToDate": "filter.toDate"
138
+ },
139
+ "OUT": "items"
140
+ }
141
+ },
142
+ {
143
+ "handleFilterChanged": {
144
+ "CALL": "apiLoadItems"
145
+ }
146
+ }
147
+ ],
148
+ "watch": {
149
+ "deep-watch": {
150
+ "filter": { "CALL": "handleFilterChanged" }
151
+ }
152
+ }
153
+ }
154
+ ```
155
+
156
+ Khi dùng pattern này:
157
+ - `filter` nên nhỏ và ổn định.
158
+ - Nếu có text search nhập liên tục, nên debounce trong `script.js`.
159
+
160
+ ## 4. Debounce khuyến nghị (script.js)
161
+
162
+ ```javascript
163
+ var filterTimer = null;
164
+
165
+ function debounceFilter(input) {
166
+ var callActionName = input && input.callActionName;
167
+ var waitMs = input && input.waitMs;
168
+ clearTimeout(filterTimer);
169
+ filterTimer = setTimeout(function () {
170
+ CALL(vueData[callActionName]);
171
+ }, waitMs || 350);
172
+ }
173
+ ```
174
+
175
+ Ví dụ action trong `module.json`:
176
+
177
+ ```json
178
+ {
179
+ "handleFilterChanged": {
180
+ "FUN": "debounceFilter",
181
+ "IN": {
182
+ "callActionName": "'apiLoadItems'",
183
+ "waitMs": 350
184
+ }
185
+ }
186
+ }
187
+ ```
188
+
189
+ ## 5. Checklist QA cho watcher
190
+
191
+ 1. Watch key có đủ nhỏ chưa (ID/field thay vì object lớn)?
192
+ 2. Có reset đúng dữ liệu phụ thuộc trước khi gọi API?
193
+ 3. Có guard `IF` trước API khi input null/rỗng?
194
+ 4. Có nguy cơ loop watcher không?
195
+ 5. Có cần debounce cho input text không?
196
+ 6. Nếu action có `IN`, đã áp dụng literal string rule cho giá trị text không khoảng trắng chưa?
@@ -0,0 +1,254 @@
1
+ # FUI Real-time Messaging — WebSocket (SignalR)
2
+
3
+ > File này sở hữu: **real-time/SignalR: `webSocketJoinGroup`, `webSocket_Send`**.
4
+
5
+ FUI cung cấp cơ chế real-time messaging dựa trên **SignalR** (không phải WebSocket API thuần của trình duyệt), host tại domain riêng `ws.fui.vn`. Toàn bộ logic client đã có sẵn trong `defaultfunction.js` (global, không cần viết lại) — chỉ cần khai báo đúng cấu trúc trong `module.json` và gọi 3 hàm sẵn có: `webSocketConnection`, `webSocketJoinGroup`, `webSocket_Send`.
6
+
7
+ ---
8
+
9
+ ## 1. Import bắt buộc
10
+
11
+ Trước khi dùng, phải thêm 2 file JS qua `file_import_new` — **đúng thứ tự** (dòng dưới phụ thuộc dòng trên):
12
+
13
+ | Thứ tự | File | Ghi chú |
14
+ |---|---|---|
15
+ | 1 (sort nhỏ hơn) | `https://ws.fui.vn/Scripts/jquery.signalR-2.4.1.min.js` | SignalR client core |
16
+ | 2 (sort lớn hơn) | `https://ws.fui.vn/signalr/hubs` | Hub proxy tự sinh từ server — vẫn là nội dung JS dù URL không có đuôi `.js` |
17
+
18
+ ```
19
+ file_import_new({
20
+ fileType: ".js", contentType: "link",
21
+ fileName: "https://ws.fui.vn/Scripts/jquery.signalR-2.4.1.min.js",
22
+ fileSort: 100
23
+ })
24
+ file_import_new({
25
+ fileType: ".js", contentType: "link",
26
+ fileName: "https://ws.fui.vn/signalr/hubs",
27
+ fileSort: 110
28
+ })
29
+ ```
30
+
31
+ > jQuery đã có sẵn trong FUI runtime — không cần import thêm. Kiểm tra `file_import_list` trước để tránh add trùng (giống quy trình `f-echart` trong [components-echart.md](components-echart.md)).
32
+
33
+ ---
34
+
35
+ ## 2. Cơ chế tự động kết nối khi module load
36
+
37
+ FUI's bootstrap (`buildApp` trong `fastproject.js`) kiểm tra `vueData.webSocket` ngay sau khi `data[]` chạy xong:
38
+
39
+ - Nếu `vueData.webSocket` tồn tại → tự tạo `vueData.webSocketState = { code, text }` và gọi `webSocketConnection('https://ws.fui.vn/signalr', vueData.webSocket, vueData.webSocketState)` + `webSocketJoinGroup(vueData.webSocket)` — **không cần tự gọi 2 hàm này ở bootstrap**, chỉ cần khai báo đúng object `webSocket` trong `data[]`.
40
+ - `webSocketState.text` phản ánh trạng thái kết nối: `Reconnecting` / `Connected` / `Disconnected`. Có thể bind trực tiếp lên UI để debug (`innerHTML: "{{webSocketState}}"`).
41
+
42
+ ---
43
+
44
+ ## 3. Khai báo group cần join — trong `data[]`
45
+
46
+ ```json
47
+ "data": [
48
+ {
49
+ "TenNhomDong": "`ThuNha",
50
+ "DGroup": "`device-abc",
51
+ "webSocket": {
52
+ "nhom1": [],
53
+ "nhom2": null,
54
+ "{{DGroup}}": {}
55
+ }
56
+ }
57
+ ]
58
+ ```
59
+
60
+ - Mỗi **key** của object `webSocket` là tên một group sẽ được join khi module load.
61
+ - **Kiểu giá trị quyết định cách nhận tin nhắn:**
62
+ - Giá trị là **array** (`[]`) → tin nhắn mới được **push** vào cuối mảng (giữ lịch sử, dùng cho chat log, danh sách sự kiện...).
63
+ - Giá trị là **`null` hoặc object** (`{}`) → tin nhắn mới **ghi đè** giá trị hiện tại (chỉ giữ tin mới nhất, dùng cho trạng thái/counter/notification đơn).
64
+ - Group name có thể là chuỗi literal (`nhom1`) hoặc **dynamic qua `{{ }}` interpolation ngay trong key** — `"{{DGroup}}"` sẽ được resolve thành giá trị hiện tại của `DGroup` (ví dụ `device-abc`), cho phép join theo group riêng của user/thiết bị.
65
+ - `` `ThuNha `` / `` `device-abc `` dùng backtick để ép literal string — xem [Value Resolution Rules](controls-patterns.md#0-value-resolution-rules-fundamental).
66
+
67
+ ---
68
+
69
+ ## 4. Join thêm group tại runtime — `FUN: webSocketJoinGroup`
70
+
71
+ Ngoài khai báo tĩnh ở mục 3, có thể join thêm group bất cứ lúc nào (kể cả ngay trong `data[]` dưới dạng action tự chạy khi load — xem [ui-patterns.md §1](ui-patterns.md)):
72
+
73
+ ```json
74
+ {
75
+ "FUN": "webSocketJoinGroup",
76
+ "IN": {
77
+ "Nhom3": null,
78
+ "{{TenNhomDong}}": []
79
+ }
80
+ }
81
+ ```
82
+
83
+ - `IN` ở đây **chính là `groupObj`** (tham số đầu tiên của `webSocketJoinGroup(groupObj, timeout, callBackFunc)`) — không map theo tên tham số như hàm khác. Vì vậy `FUN` + `IN` **không truyền được `timeout`/`callBackFunc`** — nếu cần custom timeout hoặc callback sau khi join xong, dùng `EXE` raw JS: `"EXE": "webSocketJoinGroup({Nhom3:null}, 3000, function(){ ... })"`.
84
+ - Dùng cách này khi group cần join phát sinh **có điều kiện** hoặc **sau khi** module đã load (ví dụ: trong một action, sau khi API trả về ID phòng/chat cần join), thay vì chỉ khai báo tĩnh trong `webSocket: {}`.
85
+
86
+ ---
87
+
88
+ ## 5. Nhận tin nhắn
89
+
90
+ ### watch (khuyến nghị — trigger logic khi có tin mới)
91
+
92
+ ```json
93
+ "watch": {
94
+ "webSocket.nhom1": { "MESS": "thong bao" },
95
+ "webSocket.device-abc": { "MESS": "thong bao" }
96
+ }
97
+ ```
98
+
99
+ > **Lưu ý quan trọng:** key trong `watch` **không được** dùng `{{ }}` template (watch key là literal cố định, không interpolate) — phải ghi trực tiếp giá trị đã resolve của group (ví dụ `webSocket.device-abc`, không phải `webSocket.{{DGroup}}`).
100
+
101
+ ### Bind trực tiếp lên UI (hiển thị raw, không cần watch)
102
+
103
+ ```json
104
+ {
105
+ "el": "v-textarea",
106
+ "attr": { "v-model": "webSocket.nhom2", "label": "Nội dung nhận nhom2", "rows": 5 }
107
+ }
108
+ ```
109
+
110
+ ### Debug toàn bộ state
111
+
112
+ ```json
113
+ { "el": "div", "attr": {}, "innerHTML": "{{webSocket}}" }
114
+ ```
115
+
116
+ ---
117
+
118
+ ## 6. Gửi tin nhắn — `webSocket_Send`
119
+
120
+ `webSocket_Send(obj)` nhận **một object** `{ group, data }` — `data` có thể là object, array, hoặc string, hàm tự chọn API SignalR phù hợp (`messageSendObject` / `messageSendArray` / `messageSend`).
121
+
122
+ ### Cách 1 — `FUN` + `IN` (payload object)
123
+
124
+ ```json
125
+ {
126
+ "FUN": "webSocket_Send",
127
+ "IN": {
128
+ "group": "`nhom1",
129
+ "data": { "text": "doi tuong 1", "text2": "doi tuong 2" }
130
+ }
131
+ }
132
+ ```
133
+
134
+ ### Cách 2 — `EXE` raw JS (khi cần đọc giá trị động từ vueData)
135
+
136
+ ```json
137
+ { "EXE": "webSocket_Send({ group:'nhom2', data:vueData.messText })" }
138
+ ```
139
+
140
+ ### Cách 3 — payload dạng array
141
+
142
+ ```json
143
+ {
144
+ "FUN": "webSocket_Send",
145
+ "IN": { "group": "`nhom3", "data": ["mang 1", "mang 2"] }
146
+ }
147
+ ```
148
+
149
+ ### Gọi trực tiếp trong `script.js` (ngoài action engine)
150
+
151
+ ```js
152
+ webSocket_Send({ group: 'Nhom3', data: 'Gui noi dung từ JS' })
153
+ ```
154
+
155
+ ### Cách 4 — Gửi qua HTTP API (không cần client tự giữ kết nối SignalR)
156
+
157
+ FUI cũng expose endpoint REST để gửi tin nhắn tới group mà không cần gọi `webSocket_Send` từ client:
158
+
159
+ ```json
160
+ {
161
+ "API": "https://ws.fui.vn/api/messageSend/nhom1",
162
+ "IN": { "text": "test 1", "text2": "test 2" }
163
+ }
164
+ ```
165
+
166
+ Group name cũng interpolate được trực tiếp trong URL:
167
+
168
+ ```json
169
+ { "API": "https://ws.fui.vn/api/messageSend/{{DGroup}}" }
170
+ ```
171
+
172
+ Dùng cách này khi muốn gửi tin nhắn từ một action/nơi không tiện dùng `FUN`/`EXE` (ví dụ gọi từ server, hoặc muốn tách hẳn việc gửi ra khỏi client hiện có kết nối SignalR hay không).
173
+
174
+ ---
175
+
176
+ ## 7. Ví dụ đầy đủ (tham khảo)
177
+
178
+ ```json
179
+ {
180
+ "data": [
181
+ {
182
+ "TenNhomDong": "`ThuNha",
183
+ "DGroup": "`device-abc",
184
+ "webSocket": { "nhom1": [], "nhom2": null, "{{DGroup}}": {} }
185
+ },
186
+ {
187
+ "FUN": "webSocketJoinGroup",
188
+ "IN": { "Nhom3": null, "{{TenNhomDong}}": [] }
189
+ }
190
+ ],
191
+ "watch": {
192
+ "webSocket.nhom1": { "MESS": "thong bao" },
193
+ "webSocket.device-abc": { "MESS": "thong bao" }
194
+ },
195
+ "controls": [
196
+ {
197
+ "prop": "fluid grid-list-md",
198
+ "rows": [
199
+ {
200
+ "prop": "",
201
+ "cols": [
202
+ {
203
+ "el": "v-textarea",
204
+ "col": { "class": "" },
205
+ "attr": { "v-model": "messText", "label": "noi dung", ":required": false, "rows": 5 },
206
+ "w": ""
207
+ },
208
+ {
209
+ "el": "f-button",
210
+ "col": { "class": "shrink" },
211
+ "attr": {
212
+ "label": "send - nhom2",
213
+ ":action": [
214
+ { "FUN": "webSocket_Send", "IN": { "group": "`nhom1", "data": { "text": "doi tuong 1", "text2": "doi tuong 2" } } },
215
+ { "EXE": "webSocket_Send({ group:'nhom2', data:vueData.messText})" },
216
+ { "FUN": "webSocket_Send", "IN": { "group": "`nhom3", "data": ["mang 1", "mang 2"] } }
217
+ ]
218
+ }
219
+ }
220
+ ]
221
+ },
222
+ {
223
+ "prop": "",
224
+ "cols": [
225
+ { "el": "v-textarea", "col": {}, "attr": { "v-model": "webSocket.nhom2", "label": "Nội dung nhận nhom2", ":required": false, "rows": 5 } }
226
+ ]
227
+ },
228
+ { "prop": "", "cols": [ { "el": "div", "col": {}, "attr": {}, "innerHTML": "{{webSocket}}" } ] },
229
+ {
230
+ "prop": "",
231
+ "cols": [
232
+ { "el": "f-button", "col": {}, "attr": { "label": "send by API - nhom1", ":action": [ { "API": "https://ws.fui.vn/api/messageSend/nhom1", "IN": { "text": "test 1", "text2": "test 2" } } ] } },
233
+ { "el": "f-button", "col": {}, "attr": { ":label": "'send by API - ' + DGroup", ":action": [ { "API": "https://ws.fui.vn/api/messageSend/{{DGroup}}" } ] } }
234
+ ]
235
+ },
236
+ { "prop": "", "cols": [ { "el": "div", "col": {}, "attr": {}, "innerHTML": "{{webSocketState}}" } ] }
237
+ ]
238
+ }
239
+ ],
240
+ "set": {}
241
+ }
242
+ ```
243
+
244
+ ---
245
+
246
+ ## 8. Checklist khi thêm real-time messaging vào module
247
+
248
+ - [ ] `file_import_list` kiểm tra đã có `jquery.signalR-2.4.1.min.js` + `signalr/hubs` chưa
249
+ - [ ] Thiếu → `file_import_new` cả 2, đúng thứ tự sort (signalR trước, hubs sau)
250
+ - [ ] Khai báo `data[].webSocket = { groupName: [] | null | {} }` — chọn kiểu giá trị theo nhu cầu (lịch sử vs. giá trị mới nhất)
251
+ - [ ] Group cần join runtime/điều kiện → `FUN: webSocketJoinGroup` (không dùng được `timeout`/`callBackFunc` qua `IN`, cần thì dùng `EXE`)
252
+ - [ ] Nhận tin: `watch` (key literal, không `{{ }}`) hoặc bind trực tiếp `v-model="webSocket.<group>"`
253
+ - [ ] Gửi tin: `FUN: webSocket_Send` (object/array payload) hoặc `EXE` khi cần đọc `vueData` động, hoặc `API: https://ws.fui.vn/api/messageSend/<group>` nếu muốn gửi qua HTTP thay vì client SignalR
254
+ - [ ] Không quên `vueData.webSocketState` nếu cần hiển thị trạng thái kết nối cho user