@fui-org/fui-cli 2.1.4 → 2.2.1

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 (214) hide show
  1. package/README.md +16 -7
  2. package/dist/fui-xg17j3f9.js +396 -0
  3. package/dist/fui.js +1 -1
  4. package/package.json +1 -1
  5. package/skills/fui/SKILL.md +152 -39
  6. package/skills/{fui-skill → fui}/assets/projectdefaultstyle.css +489 -489
  7. package/skills/{fui-skill → fui}/examples/component.vue +162 -162
  8. package/skills/{fui-skill → fui}/references/INDEX.md +4 -5
  9. package/skills/{fui-skill → fui}/references/advanced-techniques.md +170 -168
  10. package/skills/{fui-skill → fui}/references/component-design.md +2 -2
  11. package/skills/{fui-skill → fui}/references/component-quickref.md +78 -78
  12. package/skills/{fui-skill → fui}/references/component-table.md +287 -287
  13. package/skills/{fui-skill → fui}/references/components-dialog.md +191 -191
  14. package/skills/{fui-skill → fui}/references/components-display.md +141 -141
  15. package/skills/{fui-skill → fui}/references/components-echart.md +316 -316
  16. package/skills/{fui-skill → fui}/references/components-input.md +335 -335
  17. package/skills/{fui-skill → fui}/references/controls-patterns.md +2 -2
  18. package/skills/{fui-skill → fui}/references/controls-styling-vocabulary.md +173 -173
  19. package/skills/{fui-skill → fui}/references/db-workflow.md +9 -9
  20. package/skills/{fui-skill → fui}/references/default-function.md +456 -456
  21. package/skills/{fui-skill → fui}/references/design-modes.md +57 -57
  22. package/skills/{fui-skill → fui}/references/echart-templates.md +489 -489
  23. package/skills/{fui-skill → fui}/references/fastproject.md +99 -99
  24. package/skills/{fui-skill → fui}/references/fullstack-workflow.md +314 -313
  25. package/skills/{fui-skill → fui}/references/module-data-patterns.md +117 -117
  26. package/skills/{fui-skill → fui}/references/module-json-anatomy.md +132 -132
  27. package/skills/{fui-skill → fui}/references/module-structure.md +144 -144
  28. package/skills/{fui-skill → fui}/references/new-session.md +3 -20
  29. package/skills/{fui-skill → fui}/references/pdfmake.md +60 -60
  30. package/skills/{fui-skill → fui}/references/permission-system.md +1 -1
  31. package/skills/{fui-skill → fui}/references/platform-architecture.md +269 -269
  32. package/skills/{fui-skill → fui}/references/project-config.md +303 -303
  33. package/skills/{fui-skill → fui}/references/project-provisioning.md +4 -4
  34. package/skills/{fui-skill → fui}/references/script-map.md +262 -262
  35. package/skills/{fui-skill → fui}/references/sql-clr-functions.md +225 -225
  36. package/skills/{fui-skill → fui}/references/system-design.md +3 -3
  37. package/skills/{fui-skill → fui}/references/tapi-permission-patterns.md +156 -156
  38. package/skills/{fui-skill → fui}/references/tapi-reference.md +474 -474
  39. package/skills/fui/references/tools-registry.md +214 -0
  40. package/skills/{fui-skill → fui}/references/ui-crosswindow-patterns.md +321 -321
  41. package/skills/{fui-skill → fui}/references/ui-dialog-patterns.md +255 -255
  42. package/skills/{fui-skill → fui}/references/ui-layout-patterns.md +176 -176
  43. package/skills/{fui-skill → fui}/references/ui-patterns.md +303 -303
  44. package/skills/{fui-skill → fui}/references/ui-screenshot-review.md +95 -95
  45. package/skills/{fui-skill → fui}/references/ui-table-cell-patterns.md +318 -318
  46. package/skills/{fui-skill → fui}/references/verification.md +18 -18
  47. package/skills/{fui-skill → fui}/references/watcher-patterns.md +163 -163
  48. package/skills/{fui-skill → fui}/references/websocket-realtime.md +271 -271
  49. package/skills/{fui-skill → fui}/scripts/component-3.0.js +2715 -2715
  50. package/skills/{fui-skill → fui}/scripts/component.js +3147 -3147
  51. package/skills/{fui-skill → fui}/scripts/componentTable-3.0.js +1038 -1038
  52. package/skills/{fui-skill → fui}/scripts/componentTable.js +891 -891
  53. package/skills/{fui-skill → fui}/scripts/defaultfunction-3.0.js +866 -866
  54. package/skills/{fui-skill → fui}/scripts/defaultfunction.js +1051 -1051
  55. package/skills/{fui-skill → fui}/scripts/fastproject-3.0.js +870 -870
  56. package/skills/{fui-skill → fui}/scripts/fastproject.js +828 -828
  57. package/dist/fui-h4z7zhk2.js +0 -381
  58. package/skills/fui-skill/SKILL.md +0 -139
  59. package/skills/fui-skill/references/tools-registry.md +0 -84
  60. /package/skills/{fui-skill → fui}/assets/projectdefaultstyle-3.0.css +0 -0
  61. /package/skills/{fui-skill → fui}/design-md/airbnb/DESIGN.md +0 -0
  62. /package/skills/{fui-skill → fui}/design-md/airbnb/README.md +0 -0
  63. /package/skills/{fui-skill → fui}/design-md/airtable/DESIGN.md +0 -0
  64. /package/skills/{fui-skill → fui}/design-md/airtable/README.md +0 -0
  65. /package/skills/{fui-skill → fui}/design-md/apple/DESIGN.md +0 -0
  66. /package/skills/{fui-skill → fui}/design-md/apple/README.md +0 -0
  67. /package/skills/{fui-skill → fui}/design-md/asu/DESIGN.md +0 -0
  68. /package/skills/{fui-skill → fui}/design-md/asu/README.md +0 -0
  69. /package/skills/{fui-skill → fui}/design-md/binance/DESIGN.md +0 -0
  70. /package/skills/{fui-skill → fui}/design-md/binance/README.md +0 -0
  71. /package/skills/{fui-skill → fui}/design-md/bmw/DESIGN.md +0 -0
  72. /package/skills/{fui-skill → fui}/design-md/bmw/README.md +0 -0
  73. /package/skills/{fui-skill → fui}/design-md/bmw-m/DESIGN.md +0 -0
  74. /package/skills/{fui-skill → fui}/design-md/bmw-m/README.md +0 -0
  75. /package/skills/{fui-skill → fui}/design-md/bugatti/DESIGN.md +0 -0
  76. /package/skills/{fui-skill → fui}/design-md/bugatti/README.md +0 -0
  77. /package/skills/{fui-skill → fui}/design-md/cal/DESIGN.md +0 -0
  78. /package/skills/{fui-skill → fui}/design-md/cal/README.md +0 -0
  79. /package/skills/{fui-skill → fui}/design-md/claude/DESIGN.md +0 -0
  80. /package/skills/{fui-skill → fui}/design-md/claude/README.md +0 -0
  81. /package/skills/{fui-skill → fui}/design-md/clay/DESIGN.md +0 -0
  82. /package/skills/{fui-skill → fui}/design-md/clay/README.md +0 -0
  83. /package/skills/{fui-skill → fui}/design-md/clickhouse/DESIGN.md +0 -0
  84. /package/skills/{fui-skill → fui}/design-md/clickhouse/README.md +0 -0
  85. /package/skills/{fui-skill → fui}/design-md/cohere/DESIGN.md +0 -0
  86. /package/skills/{fui-skill → fui}/design-md/cohere/README.md +0 -0
  87. /package/skills/{fui-skill → fui}/design-md/coinbase/DESIGN.md +0 -0
  88. /package/skills/{fui-skill → fui}/design-md/coinbase/README.md +0 -0
  89. /package/skills/{fui-skill → fui}/design-md/composio/DESIGN.md +0 -0
  90. /package/skills/{fui-skill → fui}/design-md/composio/README.md +0 -0
  91. /package/skills/{fui-skill → fui}/design-md/cursor/DESIGN.md +0 -0
  92. /package/skills/{fui-skill → fui}/design-md/cursor/README.md +0 -0
  93. /package/skills/{fui-skill → fui}/design-md/elevenlabs/DESIGN.md +0 -0
  94. /package/skills/{fui-skill → fui}/design-md/elevenlabs/README.md +0 -0
  95. /package/skills/{fui-skill → fui}/design-md/expo/DESIGN.md +0 -0
  96. /package/skills/{fui-skill → fui}/design-md/expo/README.md +0 -0
  97. /package/skills/{fui-skill → fui}/design-md/ferrari/DESIGN.md +0 -0
  98. /package/skills/{fui-skill → fui}/design-md/ferrari/README.md +0 -0
  99. /package/skills/{fui-skill → fui}/design-md/figma/DESIGN.md +0 -0
  100. /package/skills/{fui-skill → fui}/design-md/figma/README.md +0 -0
  101. /package/skills/{fui-skill → fui}/design-md/framer/DESIGN.md +0 -0
  102. /package/skills/{fui-skill → fui}/design-md/framer/README.md +0 -0
  103. /package/skills/{fui-skill → fui}/design-md/fui/DESIGN.md +0 -0
  104. /package/skills/{fui-skill → fui}/design-md/hashicorp/DESIGN.md +0 -0
  105. /package/skills/{fui-skill → fui}/design-md/hashicorp/README.md +0 -0
  106. /package/skills/{fui-skill → fui}/design-md/ibm/DESIGN.md +0 -0
  107. /package/skills/{fui-skill → fui}/design-md/ibm/README.md +0 -0
  108. /package/skills/{fui-skill → fui}/design-md/intercom/DESIGN.md +0 -0
  109. /package/skills/{fui-skill → fui}/design-md/intercom/README.md +0 -0
  110. /package/skills/{fui-skill → fui}/design-md/kraken/DESIGN.md +0 -0
  111. /package/skills/{fui-skill → fui}/design-md/kraken/README.md +0 -0
  112. /package/skills/{fui-skill → fui}/design-md/lamborghini/DESIGN.md +0 -0
  113. /package/skills/{fui-skill → fui}/design-md/lamborghini/README.md +0 -0
  114. /package/skills/{fui-skill → fui}/design-md/linear.app/DESIGN.md +0 -0
  115. /package/skills/{fui-skill → fui}/design-md/linear.app/README.md +0 -0
  116. /package/skills/{fui-skill → fui}/design-md/lovable/DESIGN.md +0 -0
  117. /package/skills/{fui-skill → fui}/design-md/lovable/README.md +0 -0
  118. /package/skills/{fui-skill → fui}/design-md/mastercard/DESIGN.md +0 -0
  119. /package/skills/{fui-skill → fui}/design-md/mastercard/README.md +0 -0
  120. /package/skills/{fui-skill → fui}/design-md/meta/DESIGN.md +0 -0
  121. /package/skills/{fui-skill → fui}/design-md/meta/README.md +0 -0
  122. /package/skills/{fui-skill → fui}/design-md/minimax/DESIGN.md +0 -0
  123. /package/skills/{fui-skill → fui}/design-md/minimax/README.md +0 -0
  124. /package/skills/{fui-skill → fui}/design-md/mintlify/DESIGN.md +0 -0
  125. /package/skills/{fui-skill → fui}/design-md/mintlify/README.md +0 -0
  126. /package/skills/{fui-skill → fui}/design-md/miro/DESIGN.md +0 -0
  127. /package/skills/{fui-skill → fui}/design-md/miro/README.md +0 -0
  128. /package/skills/{fui-skill → fui}/design-md/mistral.ai/DESIGN.md +0 -0
  129. /package/skills/{fui-skill → fui}/design-md/mistral.ai/README.md +0 -0
  130. /package/skills/{fui-skill → fui}/design-md/mongodb/DESIGN.md +0 -0
  131. /package/skills/{fui-skill → fui}/design-md/mongodb/README.md +0 -0
  132. /package/skills/{fui-skill → fui}/design-md/nike/DESIGN.md +0 -0
  133. /package/skills/{fui-skill → fui}/design-md/nike/README.md +0 -0
  134. /package/skills/{fui-skill → fui}/design-md/notion/DESIGN.md +0 -0
  135. /package/skills/{fui-skill → fui}/design-md/notion/README.md +0 -0
  136. /package/skills/{fui-skill → fui}/design-md/nvidia/DESIGN.md +0 -0
  137. /package/skills/{fui-skill → fui}/design-md/nvidia/README.md +0 -0
  138. /package/skills/{fui-skill → fui}/design-md/ollama/DESIGN.md +0 -0
  139. /package/skills/{fui-skill → fui}/design-md/ollama/README.md +0 -0
  140. /package/skills/{fui-skill → fui}/design-md/opencode.ai/DESIGN.md +0 -0
  141. /package/skills/{fui-skill → fui}/design-md/opencode.ai/README.md +0 -0
  142. /package/skills/{fui-skill → fui}/design-md/pinterest/DESIGN.md +0 -0
  143. /package/skills/{fui-skill → fui}/design-md/pinterest/README.md +0 -0
  144. /package/skills/{fui-skill → fui}/design-md/playstation/DESIGN.md +0 -0
  145. /package/skills/{fui-skill → fui}/design-md/playstation/README.md +0 -0
  146. /package/skills/{fui-skill → fui}/design-md/posthog/DESIGN.md +0 -0
  147. /package/skills/{fui-skill → fui}/design-md/posthog/README.md +0 -0
  148. /package/skills/{fui-skill → fui}/design-md/raycast/DESIGN.md +0 -0
  149. /package/skills/{fui-skill → fui}/design-md/raycast/README.md +0 -0
  150. /package/skills/{fui-skill → fui}/design-md/renault/DESIGN.md +0 -0
  151. /package/skills/{fui-skill → fui}/design-md/renault/README.md +0 -0
  152. /package/skills/{fui-skill → fui}/design-md/replicate/DESIGN.md +0 -0
  153. /package/skills/{fui-skill → fui}/design-md/replicate/README.md +0 -0
  154. /package/skills/{fui-skill → fui}/design-md/resend/DESIGN.md +0 -0
  155. /package/skills/{fui-skill → fui}/design-md/resend/README.md +0 -0
  156. /package/skills/{fui-skill → fui}/design-md/revolut/DESIGN.md +0 -0
  157. /package/skills/{fui-skill → fui}/design-md/revolut/README.md +0 -0
  158. /package/skills/{fui-skill → fui}/design-md/runwayml/DESIGN.md +0 -0
  159. /package/skills/{fui-skill → fui}/design-md/runwayml/README.md +0 -0
  160. /package/skills/{fui-skill → fui}/design-md/sanity/DESIGN.md +0 -0
  161. /package/skills/{fui-skill → fui}/design-md/sanity/README.md +0 -0
  162. /package/skills/{fui-skill → fui}/design-md/sentry/DESIGN.md +0 -0
  163. /package/skills/{fui-skill → fui}/design-md/sentry/README.md +0 -0
  164. /package/skills/{fui-skill → fui}/design-md/shopify/DESIGN.md +0 -0
  165. /package/skills/{fui-skill → fui}/design-md/shopify/README.md +0 -0
  166. /package/skills/{fui-skill → fui}/design-md/slack/DESIGN.md +0 -0
  167. /package/skills/{fui-skill → fui}/design-md/spacex/DESIGN.md +0 -0
  168. /package/skills/{fui-skill → fui}/design-md/spacex/README.md +0 -0
  169. /package/skills/{fui-skill → fui}/design-md/spotify/DESIGN.md +0 -0
  170. /package/skills/{fui-skill → fui}/design-md/spotify/README.md +0 -0
  171. /package/skills/{fui-skill → fui}/design-md/starbucks/DESIGN.md +0 -0
  172. /package/skills/{fui-skill → fui}/design-md/starbucks/README.md +0 -0
  173. /package/skills/{fui-skill → fui}/design-md/stripe/DESIGN.md +0 -0
  174. /package/skills/{fui-skill → fui}/design-md/stripe/README.md +0 -0
  175. /package/skills/{fui-skill → fui}/design-md/supabase/DESIGN.md +0 -0
  176. /package/skills/{fui-skill → fui}/design-md/supabase/README.md +0 -0
  177. /package/skills/{fui-skill → fui}/design-md/superhuman/DESIGN.md +0 -0
  178. /package/skills/{fui-skill → fui}/design-md/superhuman/README.md +0 -0
  179. /package/skills/{fui-skill → fui}/design-md/tesla/DESIGN.md +0 -0
  180. /package/skills/{fui-skill → fui}/design-md/tesla/README.md +0 -0
  181. /package/skills/{fui-skill → fui}/design-md/theverge/DESIGN.md +0 -0
  182. /package/skills/{fui-skill → fui}/design-md/theverge/README.md +0 -0
  183. /package/skills/{fui-skill → fui}/design-md/together.ai/DESIGN.md +0 -0
  184. /package/skills/{fui-skill → fui}/design-md/together.ai/README.md +0 -0
  185. /package/skills/{fui-skill → fui}/design-md/uber/DESIGN.md +0 -0
  186. /package/skills/{fui-skill → fui}/design-md/uber/README.md +0 -0
  187. /package/skills/{fui-skill → fui}/design-md/vercel/DESIGN.md +0 -0
  188. /package/skills/{fui-skill → fui}/design-md/vercel/README.md +0 -0
  189. /package/skills/{fui-skill → fui}/design-md/vodafone/DESIGN.md +0 -0
  190. /package/skills/{fui-skill → fui}/design-md/vodafone/README.md +0 -0
  191. /package/skills/{fui-skill → fui}/design-md/voltagent/DESIGN.md +0 -0
  192. /package/skills/{fui-skill → fui}/design-md/voltagent/README.md +0 -0
  193. /package/skills/{fui-skill → fui}/design-md/warp/DESIGN.md +0 -0
  194. /package/skills/{fui-skill → fui}/design-md/warp/README.md +0 -0
  195. /package/skills/{fui-skill → fui}/design-md/webflow/DESIGN.md +0 -0
  196. /package/skills/{fui-skill → fui}/design-md/webflow/README.md +0 -0
  197. /package/skills/{fui-skill → fui}/design-md/wired/DESIGN.md +0 -0
  198. /package/skills/{fui-skill → fui}/design-md/wired/README.md +0 -0
  199. /package/skills/{fui-skill → fui}/design-md/wise/DESIGN.md +0 -0
  200. /package/skills/{fui-skill → fui}/design-md/wise/README.md +0 -0
  201. /package/skills/{fui-skill → fui}/design-md/x.ai/DESIGN.md +0 -0
  202. /package/skills/{fui-skill → fui}/design-md/x.ai/README.md +0 -0
  203. /package/skills/{fui-skill → fui}/design-md/zapier/DESIGN.md +0 -0
  204. /package/skills/{fui-skill → fui}/design-md/zapier/README.md +0 -0
  205. /package/skills/{fui-skill → fui}/examples/f-table-patterns.json +0 -0
  206. /package/skills/{fui-skill → fui}/examples/module-patterns.json +0 -0
  207. /package/skills/{fui-skill → fui}/examples/project-patterns.json +0 -0
  208. /package/skills/{fui-skill → fui}/references/coding-standards.md +0 -0
  209. /package/skills/{fui-skill → fui}/references/db-table-design.md +0 -0
  210. /package/skills/{fui-skill → fui}/references/fsheet.md +0 -0
  211. /package/skills/{fui-skill → fui}/references/tapi-file-api.md +0 -0
  212. /package/skills/{fui-skill → fui}/references/ui-templates.md +0 -0
  213. /package/skills/{fui-skill → fui}/scripts/fechart.js +0 -0
  214. /package/skills/{fui-skill → fui}/scripts/fsheet.js +0 -0
@@ -1,456 +1,456 @@
1
- # Default Functions
2
-
3
- > Owns: **FUI utility functions usable in `FUN`/`EXE`/`script.js`** (openWindow, ajaxCALL, jsonToExcel, groupBy...). Source locations: [script-map.md](script-map.md).
4
-
5
- Globals defined in `defaultfunction.js`, auto-loaded in every FUI V2 module.
6
-
7
- ---
8
-
9
- ## Global Variables
10
-
11
- | Variable | Meaning |
12
- | ----------- | ------------------------------------------- |
13
- | `$isMobile` | `true` if mobile user agent |
14
- | `$isPhone` | `true` if phone (not tablet) |
15
- | `$isApple` | `true` if Apple device (iPad, iPhone, iPod) |
16
-
17
- ---
18
-
19
- ## Dialogs & Messages
20
-
21
- ### confirm(obj)
22
-
23
- Styled OK/Cancel dialog; wraps `$.confirm` (jquery-confirm).
24
-
25
- | Param | Meaning |
26
- | --------- | --------------------------------------------------------- |
27
- | `title` | Title |
28
- | `message` | Body |
29
- | `action` | Function run on OK |
30
- | `cancel` | Function run on Cancel (optional) |
31
- | `type` | Color: `'blue'` (default), `'red'`, `'green'`, `'orange'` |
32
- | `icon` | Icon class, e.g. `'mdi mdi-delete'` |
33
-
34
- ```js
35
- confirm({
36
- title: "Xác nhận xóa",
37
- message: "Bạn có chắc muốn xóa bản ghi này không?",
38
- type: "red",
39
- icon: "mdi mdi-delete",
40
- action: function () {
41
- CALL(vueData.actDelete);
42
- },
43
- });
44
- ```
45
-
46
- ---
47
-
48
- ### showMessage(obj)
49
-
50
- Message dialog (Close button only); wraps `$.dialog`.
51
-
52
- | Param | Meaning |
53
- | --------- | ----------------------------------- |
54
- | `title` | Title |
55
- | `message` | Body |
56
- | `type` | Color: `'blue'`, `'red'`, `'green'` |
57
- | `icon` | Icon class |
58
- | `onclose` | FUI action object run after close |
59
-
60
- ```js
61
- showMessage({ title: "Thông báo", message: "Lưu thành công!" });
62
-
63
- showMessage({
64
- title: "Lỗi",
65
- message: "Không tìm thấy dữ liệu",
66
- type: "red",
67
- icon: "mdi mdi-alert-circle",
68
- });
69
-
70
- // Chạy action sau khi đóng
71
- showMessage({
72
- title: "Hoàn tất",
73
- message: "Import xong!",
74
- onclose: vueData.actReload,
75
- });
76
- ```
77
-
78
- ---
79
-
80
- ## Navigation & Window
81
-
82
- ### pushRouter(router)
83
-
84
- Updates the browser URL without reload — sync URL with UI state.
85
-
86
- ```js
87
- pushRouter("/fp/don-hang/danh-sach");
88
- ```
89
-
90
- ---
91
-
92
- ### openWindow(obj)
93
-
94
- Opens an FUI window (`f-window`) overlay.
95
-
96
- | Param | Meaning |
97
- | -------- | --------------------------------------------------------- |
98
- | `url` | Content URL (required) |
99
- | `title` | Window title |
100
- | `id` | Identifier (auto if omitted) — prevents duplicate windows |
101
- | `width` | Width (px or %) |
102
- | `height` | Height |
103
-
104
- ```js
105
- // Cách đơn giản — chỉ truyền URL
106
- openWindow("/fp/san-pham/chi-tiet?id=123");
107
-
108
- // Đầy đủ options
109
- openWindow({
110
- id: "winChiTiet",
111
- url: "/fp/san-pham/chi-tiet?id=" + vueData.selectedId,
112
- title: "Chi tiết sản phẩm",
113
- width: "80%",
114
- });
115
- ```
116
-
117
- > Called inside an `f-window` (iframe) → FUI switches to `windowSendMessage('#PARENT', 'openWindow', obj)` to open in the parent.
118
-
119
- ---
120
-
121
- ### redirect(obj, query)
122
-
123
- Navigates the current page to another URL.
124
-
125
- ```js
126
- redirect("/fp/trang-chu");
127
- redirect({ url: "/fp/don-hang", query: true }); // giữ lại query params hiện tại
128
- ```
129
-
130
- ---
131
-
132
- ### reload()
133
-
134
- Reloads the page.
135
-
136
- ```js
137
- reload();
138
- ```
139
-
140
- ---
141
-
142
- ### windowSendMessage(window, cmd, data)
143
-
144
- `postMessage` to another window/iframe — communication between `f-window`s.
145
-
146
- | Param | Meaning |
147
- | -------- | ----------------------------------------------------------------- |
148
- | `window` | Window object, `'#PARENT'` (parent), or `'#iframeId'` (iframe ID) |
149
- | `cmd` | Command name |
150
- | `data` | Payload |
151
-
152
- ```js
153
- // Gửi lên cửa sổ cha
154
- windowSendMessage("#PARENT", "refreshData", { id: vueData.selectedId });
155
-
156
- // Gửi đến iframe có id="iframeChild"
157
- windowSendMessage("#iframeChild", "loadItem", { itemId: 5 });
158
- ```
159
-
160
- ---
161
-
162
- ## AJAX
163
-
164
- ### ajaxCALL(URL, DATA, callBack, errorCallBack, header)
165
-
166
- AJAX POST. Pass only the path — domain is added via `fixURL`.
167
-
168
- ```js
169
- ajaxCALL("/SP_GetDonHang", { maKH: vueData.maKH }, function (res) {
170
- vueData.danhSach = res.data;
171
- });
172
-
173
- // Với error handler
174
- ajaxCALL(
175
- "/SP_SaveDonHang",
176
- vueData.form,
177
- function (res) {
178
- Vue.$toast.success("Lưu thành công", { position: "top" });
179
- },
180
- function (err) {
181
- showMessage({ title: "Lỗi", message: err.message, type: "red" });
182
- },
183
- );
184
- ```
185
-
186
- ---
187
-
188
- ## File Export
189
-
190
- ### jsonToExcel(Obj)
191
-
192
- Exports JSON to `.xlsx`. Auto-loads ExcelJS.
193
-
194
- **Single sheet:**
195
-
196
- ```js
197
- jsonToExcel({
198
- data: vueData.danhSach, // Array of objects
199
- filename: "danh-sach.xlsx",
200
- worksheet: "DanhSach",
201
- });
202
- ```
203
-
204
- **Multi sheet:**
205
-
206
- ```js
207
- jsonToExcel({
208
- filename: "bao-cao.xlsx",
209
- sheets: [
210
- { name: "Sheet1", data: vueData.list1 },
211
- { name: "Sheet2", data: vueData.list2 },
212
- ],
213
- });
214
- ```
215
-
216
- Without `columns`, the columns are the keys of the **first row**, headers are the key names, values are raw. A field missing from the first row is missing from the whole file.
217
-
218
- **Pick columns, headers and formats — `columns`** (`f-table excel="UI"` also uses `columns`, but only `header`/`key`, no formatting):
219
-
220
- ```js
221
- jsonToExcel({
222
- data: vueData.dsHang, // the original array of objects — no need to map each row
223
- filename: "kho.xlsx",
224
- worksheet: "Kho",
225
- columns: [
226
- { header: "Tên hàng", key: "Ten" },
227
- { header: "Số lượng", key: "SoLuong", type: "number", numFmt: "#,##0", sum: true },
228
- { header: "Thành tiền", key: "ThanhTien", type: "number", numFmt: "#,##0.00", sum: "#,##0" },
229
- { header: "Ngày nhập", key: "NgayNhap", type: "date", numFmt: "dd/mm/yyyy" },
230
- { header: "Ghi chú", key: "GhiChuHtml", type: "html", width: 40 },
231
- ],
232
- });
233
- ```
234
-
235
- | Field | Meaning |
236
- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
237
- | `header` | Column title. May repeat (two "Tổng" columns do not overwrite each other) |
238
- | `key` | Row field; nested paths `a.b` are supported |
239
- | `type` | `'number'`: numeric string → number. `'date'`: ISO/`yyyy-mm-dd` string → real date in **local time** (no one-day shift from UTC). `'html'`: strip tags, keep text. Omitted: as is (object/array → JSON string) |
240
- | `numFmt` | **Excel** format code (not numeral/moment): `#,##0`, `#,##0.00`, `0.0%`, `dd/mm/yyyy hh:mm`. `'#,##0;-#,##0;'` hides zeros but keeps the value |
241
- | `width` | Width in characters; omitted, it is estimated from the first 100 rows |
242
- | `sum` | `true` or a numFmt → adds a total row at the end of the sheet with a `SUM` formula over the exported rows |
243
-
244
- - With `columns`, the header row is bold and frozen. Each sheet in `sheets` can have its own `columns`: `{ name, data, columns }`.
245
- - Only fields listed in `columns` go into the file. This is the clean export (no IDs or technical fields leak) and it is lighter than mapping to a new array of objects just to rename keys.
246
-
247
- ---
248
-
249
- ## Data Utilities
250
-
251
- ### groupBy(arrayObj, groupField, sumArr)
252
-
253
- Groups objects by a field and sums numeric columns; returns grouped array with `count`.
254
-
255
- ```js
256
- // groupBy(data, 'Khoa', ['SLNghiHoc', 'SLTotNghiep'])
257
- var result = groupBy(vueData.data, "phongBan", ["soLuong", "doanhThu"]);
258
- // result: [{ phongBan: 'A', soLuong: 10, doanhThu: 5000, count: 3 }, ...]
259
- ```
260
-
261
- > For complex grouping use Lodash `_.groupBy`.
262
-
263
- ---
264
-
265
- ### findInArray(obj)
266
-
267
- Finds an element by condition.
268
-
269
- ```js
270
- var item = findInArray({ array: vueData.list, value: { id: 5 } });
271
- ```
272
-
273
- > Usually prefer the shorter Lodash `_.find(array, condition)`.
274
-
275
- ---
276
-
277
- ### fillData(obj)
278
-
279
- Copies data from a source array into a target object by key and mapping.
280
-
281
- - **Parameters:** `{ src, des, key, map }` — src: source array, des: target object, key: matching value, map: field mapping
282
-
283
- ---
284
-
285
- ### buildHeader(obj, mapCol)
286
-
287
- Builds `headers` for `f-table` from data. No `mapCol` → headers from the first record's keys (numeric columns get `el: 't-num'`); with `mapCol` → mapped from `{ text, value }`.
288
-
289
- ```js
290
- // Tự dựng header từ dữ liệu API
291
- vueData.headers = buildHeader(vueData.dsData);
292
- ```
293
-
294
- ### capacityText(numb)
295
-
296
- Bytes → size string (`'1.5 Mb'`, `'320 Kb'`) for file sizes.
297
-
298
- ### appCommand(cmdData)
299
-
300
- Sends a command to the native app (React Native / iOS webkit / Android) when FUI is embedded in a mobile app.
301
-
302
- ---
303
-
304
- ## PDF
305
-
306
- ### printPDF(obj)
307
-
308
- Builds and prints/downloads a PDF with `pdfMake`. Auto-loads pdfMake.
309
-
310
- ```js
311
- printPDF({
312
- data: docDefinition, // pdfMake docDefinition object
313
- download: true, // true = tải file, false = mở viewer
314
- callBack: function () {
315
- console.log("Done");
316
- },
317
- });
318
- ```
319
-
320
- ---
321
-
322
- ## Clipboard & String
323
-
324
- ### copyToClipboard(textToCopy)
325
-
326
- Copies to clipboard; uses `navigator.clipboard` when available (HTTPS), else `execCommand`.
327
-
328
- ```js
329
- copyToClipboard(vueData.maHoaDon);
330
- copyToClipboard(JSON.stringify(vueData.data));
331
- ```
332
-
333
- ---
334
-
335
- ### fileNameClear(fname)
336
-
337
- Strips special characters from a file name (keeps letters, digits, spaces, dashes).
338
-
339
- ```js
340
- var safeName = fileNameClear(vueData.tenFile); // "report (2025)!" → "report 2025"
341
- ```
342
-
343
- ---
344
-
345
- ### hashCode(s)
346
-
347
- Integer hash of a string (for unique keys).
348
-
349
- ```js
350
- var key = hashCode("unique-string-" + Date.now());
351
- ```
352
-
353
- ---
354
-
355
- ### generateID()
356
-
357
- Random string ID.
358
-
359
- ```js
360
- var newId = "win_" + generateID();
361
- ```
362
-
363
- ---
364
-
365
- ## Cookie & Auth
366
-
367
- ### getCookie(cname) / setCookie(name, value, days, domain)
368
-
369
- Read/write browser cookies.
370
-
371
- ```js
372
- var token = getCookie("awt");
373
- setCookie("theme", "dark", 30); // hết hạn sau 30 ngày
374
- setCookie("lang", "vi", 365, ".fui.vn"); // share cookie toàn domain
375
- ```
376
-
377
- ---
378
-
379
- ### logout(cookiesName, domain, url)
380
-
381
- Clears the cookie and redirects to login.
382
-
383
- ```js
384
- logout(); // xóa cookie 'awt', redirect về login mặc định
385
- logout("myToken", ".fui.vn", "/login");
386
- ```
387
-
388
- ---
389
-
390
- ## Permissions
391
-
392
- ### rightTest(rightObject)
393
-
394
- Checks whether the current user has the required rights, against `vueData.user`.
395
-
396
- ```js
397
- // Kiểm tra user có role 'admin' hoặc 'manager'
398
- if (rightTest({ role: ['admin', 'manager'] })) {
399
- vueData.showDelete = true;
400
- }
401
-
402
- // Kiểm tra nhiều điều kiện cùng lúc (AND)
403
- if (rightTest({ dept: 'IT', level: [1, 2] })) { ... }
404
- ```
405
-
406
- ---
407
-
408
- ## Color Utilities
409
-
410
- ### colorLib(color)
411
-
412
- Color utility (hex, rgb, hsl), mostly for charts.
413
-
414
- ```js
415
- var c = colorLib("#4e73df");
416
- c.hexString(); // '#4e73df'
417
- c.rgbString(); // 'rgb(78, 115, 223)'
418
- c.lighten(0.2); // làm sáng 20%
419
- c.darken(0.1); // làm tối 10%
420
- c.alpha(0.5).rgbString(); // 'rgba(78, 115, 223, 0.5)'
421
- ```
422
-
423
- ### transparentize(value, opacity)
424
-
425
- Shortcut for a transparent color.
426
-
427
- ```js
428
- transparentize("#4e73df", 0.5); // 'rgba(78, 115, 223, 0.5)'
429
- ```
430
-
431
- ---
432
-
433
- ## Script Loader
434
-
435
- ### loadScripts(scriptsArray, callbackFunc)
436
-
437
- Loads scripts by URL, then runs the callback — on-demand libraries.
438
-
439
- ```js
440
- loadScripts(["/include/pdfmake/pdfmake.min.js", "/include/pdfmake/vfs_fonts.js"], function () {
441
- printPDF({ data: docDef, download: true });
442
- });
443
- ```
444
-
445
- ---
446
-
447
- ## Device Detection
448
-
449
- ### extractHostname(url) / getDomainWithoutSubdomain(url)
450
-
451
- Extract hostname/domain from a URL. Internal; rarely used directly.
452
-
453
- ```js
454
- extractHostname("https://api.fui.vn/fp/test"); // 'api.fui.vn'
455
- getDomainWithoutSubdomain("https://api.fui.vn"); // 'fui.vn'
456
- ```
1
+ # Default Functions
2
+
3
+ > Owns: **FUI utility functions usable in `FUN`/`EXE`/`script.js`** (openWindow, ajaxCALL, jsonToExcel, groupBy...). Source locations: [script-map.md](script-map.md).
4
+
5
+ Globals defined in `defaultfunction.js`, auto-loaded in every FUI V2 module.
6
+
7
+ ---
8
+
9
+ ## Global Variables
10
+
11
+ | Variable | Meaning |
12
+ | ----------- | ------------------------------------------- |
13
+ | `$isMobile` | `true` if mobile user agent |
14
+ | `$isPhone` | `true` if phone (not tablet) |
15
+ | `$isApple` | `true` if Apple device (iPad, iPhone, iPod) |
16
+
17
+ ---
18
+
19
+ ## Dialogs & Messages
20
+
21
+ ### confirm(obj)
22
+
23
+ Styled OK/Cancel dialog; wraps `$.confirm` (jquery-confirm).
24
+
25
+ | Param | Meaning |
26
+ | --------- | --------------------------------------------------------- |
27
+ | `title` | Title |
28
+ | `message` | Body |
29
+ | `action` | Function run on OK |
30
+ | `cancel` | Function run on Cancel (optional) |
31
+ | `type` | Color: `'blue'` (default), `'red'`, `'green'`, `'orange'` |
32
+ | `icon` | Icon class, e.g. `'mdi mdi-delete'` |
33
+
34
+ ```js
35
+ confirm({
36
+ title: "Xác nhận xóa",
37
+ message: "Bạn có chắc muốn xóa bản ghi này không?",
38
+ type: "red",
39
+ icon: "mdi mdi-delete",
40
+ action: function () {
41
+ CALL(vueData.actDelete);
42
+ },
43
+ });
44
+ ```
45
+
46
+ ---
47
+
48
+ ### showMessage(obj)
49
+
50
+ Message dialog (Close button only); wraps `$.dialog`.
51
+
52
+ | Param | Meaning |
53
+ | --------- | ----------------------------------- |
54
+ | `title` | Title |
55
+ | `message` | Body |
56
+ | `type` | Color: `'blue'`, `'red'`, `'green'` |
57
+ | `icon` | Icon class |
58
+ | `onclose` | FUI action object run after close |
59
+
60
+ ```js
61
+ showMessage({ title: "Thông báo", message: "Lưu thành công!" });
62
+
63
+ showMessage({
64
+ title: "Lỗi",
65
+ message: "Không tìm thấy dữ liệu",
66
+ type: "red",
67
+ icon: "mdi mdi-alert-circle",
68
+ });
69
+
70
+ // Chạy action sau khi đóng
71
+ showMessage({
72
+ title: "Hoàn tất",
73
+ message: "Import xong!",
74
+ onclose: vueData.actReload,
75
+ });
76
+ ```
77
+
78
+ ---
79
+
80
+ ## Navigation & Window
81
+
82
+ ### pushRouter(router)
83
+
84
+ Updates the browser URL without reload — sync URL with UI state.
85
+
86
+ ```js
87
+ pushRouter("/fp/don-hang/danh-sach");
88
+ ```
89
+
90
+ ---
91
+
92
+ ### openWindow(obj)
93
+
94
+ Opens an FUI window (`f-window`) overlay.
95
+
96
+ | Param | Meaning |
97
+ | -------- | --------------------------------------------------------- |
98
+ | `url` | Content URL (required) |
99
+ | `title` | Window title |
100
+ | `id` | Identifier (auto if omitted) — prevents duplicate windows |
101
+ | `width` | Width (px or %) |
102
+ | `height` | Height |
103
+
104
+ ```js
105
+ // Cách đơn giản — chỉ truyền URL
106
+ openWindow("/fp/san-pham/chi-tiet?id=123");
107
+
108
+ // Đầy đủ options
109
+ openWindow({
110
+ id: "winChiTiet",
111
+ url: "/fp/san-pham/chi-tiet?id=" + vueData.selectedId,
112
+ title: "Chi tiết sản phẩm",
113
+ width: "80%",
114
+ });
115
+ ```
116
+
117
+ > Called inside an `f-window` (iframe) → FUI switches to `windowSendMessage('#PARENT', 'openWindow', obj)` to open in the parent.
118
+
119
+ ---
120
+
121
+ ### redirect(obj, query)
122
+
123
+ Navigates the current page to another URL.
124
+
125
+ ```js
126
+ redirect("/fp/trang-chu");
127
+ redirect({ url: "/fp/don-hang", query: true }); // giữ lại query params hiện tại
128
+ ```
129
+
130
+ ---
131
+
132
+ ### reload()
133
+
134
+ Reloads the page.
135
+
136
+ ```js
137
+ reload();
138
+ ```
139
+
140
+ ---
141
+
142
+ ### windowSendMessage(window, cmd, data)
143
+
144
+ `postMessage` to another window/iframe — communication between `f-window`s.
145
+
146
+ | Param | Meaning |
147
+ | -------- | ----------------------------------------------------------------- |
148
+ | `window` | Window object, `'#PARENT'` (parent), or `'#iframeId'` (iframe ID) |
149
+ | `cmd` | Command name |
150
+ | `data` | Payload |
151
+
152
+ ```js
153
+ // Gửi lên cửa sổ cha
154
+ windowSendMessage("#PARENT", "refreshData", { id: vueData.selectedId });
155
+
156
+ // Gửi đến iframe có id="iframeChild"
157
+ windowSendMessage("#iframeChild", "loadItem", { itemId: 5 });
158
+ ```
159
+
160
+ ---
161
+
162
+ ## AJAX
163
+
164
+ ### ajaxCALL(URL, DATA, callBack, errorCallBack, header)
165
+
166
+ AJAX POST. Pass only the path — domain is added via `fixURL`.
167
+
168
+ ```js
169
+ ajaxCALL("/SP_GetDonHang", { maKH: vueData.maKH }, function (res) {
170
+ vueData.danhSach = res.data;
171
+ });
172
+
173
+ // Với error handler
174
+ ajaxCALL(
175
+ "/SP_SaveDonHang",
176
+ vueData.form,
177
+ function (res) {
178
+ Vue.$toast.success("Lưu thành công", { position: "top" });
179
+ },
180
+ function (err) {
181
+ showMessage({ title: "Lỗi", message: err.message, type: "red" });
182
+ },
183
+ );
184
+ ```
185
+
186
+ ---
187
+
188
+ ## File Export
189
+
190
+ ### jsonToExcel(Obj)
191
+
192
+ Exports JSON to `.xlsx`. Auto-loads ExcelJS.
193
+
194
+ **Single sheet:**
195
+
196
+ ```js
197
+ jsonToExcel({
198
+ data: vueData.danhSach, // Array of objects
199
+ filename: "danh-sach.xlsx",
200
+ worksheet: "DanhSach",
201
+ });
202
+ ```
203
+
204
+ **Multi sheet:**
205
+
206
+ ```js
207
+ jsonToExcel({
208
+ filename: "bao-cao.xlsx",
209
+ sheets: [
210
+ { name: "Sheet1", data: vueData.list1 },
211
+ { name: "Sheet2", data: vueData.list2 },
212
+ ],
213
+ });
214
+ ```
215
+
216
+ Without `columns`, the columns are the keys of the **first row**, headers are the key names, values are raw. A field missing from the first row is missing from the whole file.
217
+
218
+ **Pick columns, headers and formats — `columns`** (`f-table excel="UI"` also uses `columns`, but only `header`/`key`, no formatting):
219
+
220
+ ```js
221
+ jsonToExcel({
222
+ data: vueData.dsHang, // the original array of objects — no need to map each row
223
+ filename: "kho.xlsx",
224
+ worksheet: "Kho",
225
+ columns: [
226
+ { header: "Tên hàng", key: "Ten" },
227
+ { header: "Số lượng", key: "SoLuong", type: "number", numFmt: "#,##0", sum: true },
228
+ { header: "Thành tiền", key: "ThanhTien", type: "number", numFmt: "#,##0.00", sum: "#,##0" },
229
+ { header: "Ngày nhập", key: "NgayNhap", type: "date", numFmt: "dd/mm/yyyy" },
230
+ { header: "Ghi chú", key: "GhiChuHtml", type: "html", width: 40 },
231
+ ],
232
+ });
233
+ ```
234
+
235
+ | Field | Meaning |
236
+ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
237
+ | `header` | Column title. May repeat (two "Tổng" columns do not overwrite each other) |
238
+ | `key` | Row field; nested paths `a.b` are supported |
239
+ | `type` | `'number'`: numeric string → number. `'date'`: ISO/`yyyy-mm-dd` string → real date in **local time** (no one-day shift from UTC). `'html'`: strip tags, keep text. Omitted: as is (object/array → JSON string) |
240
+ | `numFmt` | **Excel** format code (not numeral/moment): `#,##0`, `#,##0.00`, `0.0%`, `dd/mm/yyyy hh:mm`. `'#,##0;-#,##0;'` hides zeros but keeps the value |
241
+ | `width` | Width in characters; omitted, it is estimated from the first 100 rows |
242
+ | `sum` | `true` or a numFmt → adds a total row at the end of the sheet with a `SUM` formula over the exported rows |
243
+
244
+ - With `columns`, the header row is bold and frozen. Each sheet in `sheets` can have its own `columns`: `{ name, data, columns }`.
245
+ - Only fields listed in `columns` go into the file. This is the clean export (no IDs or technical fields leak) and it is lighter than mapping to a new array of objects just to rename keys.
246
+
247
+ ---
248
+
249
+ ## Data Utilities
250
+
251
+ ### groupBy(arrayObj, groupField, sumArr)
252
+
253
+ Groups objects by a field and sums numeric columns; returns grouped array with `count`.
254
+
255
+ ```js
256
+ // groupBy(data, 'Khoa', ['SLNghiHoc', 'SLTotNghiep'])
257
+ var result = groupBy(vueData.data, "phongBan", ["soLuong", "doanhThu"]);
258
+ // result: [{ phongBan: 'A', soLuong: 10, doanhThu: 5000, count: 3 }, ...]
259
+ ```
260
+
261
+ > For complex grouping use Lodash `_.groupBy`.
262
+
263
+ ---
264
+
265
+ ### findInArray(obj)
266
+
267
+ Finds an element by condition.
268
+
269
+ ```js
270
+ var item = findInArray({ array: vueData.list, value: { id: 5 } });
271
+ ```
272
+
273
+ > Usually prefer the shorter Lodash `_.find(array, condition)`.
274
+
275
+ ---
276
+
277
+ ### fillData(obj)
278
+
279
+ Copies data from a source array into a target object by key and mapping.
280
+
281
+ - **Parameters:** `{ src, des, key, map }` — src: source array, des: target object, key: matching value, map: field mapping
282
+
283
+ ---
284
+
285
+ ### buildHeader(obj, mapCol)
286
+
287
+ Builds `headers` for `f-table` from data. No `mapCol` → headers from the first record's keys (numeric columns get `el: 't-num'`); with `mapCol` → mapped from `{ text, value }`.
288
+
289
+ ```js
290
+ // Tự dựng header từ dữ liệu API
291
+ vueData.headers = buildHeader(vueData.dsData);
292
+ ```
293
+
294
+ ### capacityText(numb)
295
+
296
+ Bytes → size string (`'1.5 Mb'`, `'320 Kb'`) for file sizes.
297
+
298
+ ### appCommand(cmdData)
299
+
300
+ Sends a command to the native app (React Native / iOS webkit / Android) when FUI is embedded in a mobile app.
301
+
302
+ ---
303
+
304
+ ## PDF
305
+
306
+ ### printPDF(obj)
307
+
308
+ Builds and prints/downloads a PDF with `pdfMake`. Auto-loads pdfMake.
309
+
310
+ ```js
311
+ printPDF({
312
+ data: docDefinition, // pdfMake docDefinition object
313
+ download: true, // true = tải file, false = mở viewer
314
+ callBack: function () {
315
+ console.log("Done");
316
+ },
317
+ });
318
+ ```
319
+
320
+ ---
321
+
322
+ ## Clipboard & String
323
+
324
+ ### copyToClipboard(textToCopy)
325
+
326
+ Copies to clipboard; uses `navigator.clipboard` when available (HTTPS), else `execCommand`.
327
+
328
+ ```js
329
+ copyToClipboard(vueData.maHoaDon);
330
+ copyToClipboard(JSON.stringify(vueData.data));
331
+ ```
332
+
333
+ ---
334
+
335
+ ### fileNameClear(fname)
336
+
337
+ Strips special characters from a file name (keeps letters, digits, spaces, dashes).
338
+
339
+ ```js
340
+ var safeName = fileNameClear(vueData.tenFile); // "report (2025)!" → "report 2025"
341
+ ```
342
+
343
+ ---
344
+
345
+ ### hashCode(s)
346
+
347
+ Integer hash of a string (for unique keys).
348
+
349
+ ```js
350
+ var key = hashCode("unique-string-" + Date.now());
351
+ ```
352
+
353
+ ---
354
+
355
+ ### generateID()
356
+
357
+ Random string ID.
358
+
359
+ ```js
360
+ var newId = "win_" + generateID();
361
+ ```
362
+
363
+ ---
364
+
365
+ ## Cookie & Auth
366
+
367
+ ### getCookie(cname) / setCookie(name, value, days, domain)
368
+
369
+ Read/write browser cookies.
370
+
371
+ ```js
372
+ var token = getCookie("awt");
373
+ setCookie("theme", "dark", 30); // hết hạn sau 30 ngày
374
+ setCookie("lang", "vi", 365, ".fui.vn"); // share cookie toàn domain
375
+ ```
376
+
377
+ ---
378
+
379
+ ### logout(cookiesName, domain, url)
380
+
381
+ Clears the cookie and redirects to login.
382
+
383
+ ```js
384
+ logout(); // xóa cookie 'awt', redirect về login mặc định
385
+ logout("myToken", ".fui.vn", "/login");
386
+ ```
387
+
388
+ ---
389
+
390
+ ## Permissions
391
+
392
+ ### rightTest(rightObject)
393
+
394
+ Checks whether the current user has the required rights, against `vueData.user`.
395
+
396
+ ```js
397
+ // Kiểm tra user có role 'admin' hoặc 'manager'
398
+ if (rightTest({ role: ['admin', 'manager'] })) {
399
+ vueData.showDelete = true;
400
+ }
401
+
402
+ // Kiểm tra nhiều điều kiện cùng lúc (AND)
403
+ if (rightTest({ dept: 'IT', level: [1, 2] })) { ... }
404
+ ```
405
+
406
+ ---
407
+
408
+ ## Color Utilities
409
+
410
+ ### colorLib(color)
411
+
412
+ Color utility (hex, rgb, hsl), mostly for charts.
413
+
414
+ ```js
415
+ var c = colorLib("#4e73df");
416
+ c.hexString(); // '#4e73df'
417
+ c.rgbString(); // 'rgb(78, 115, 223)'
418
+ c.lighten(0.2); // làm sáng 20%
419
+ c.darken(0.1); // làm tối 10%
420
+ c.alpha(0.5).rgbString(); // 'rgba(78, 115, 223, 0.5)'
421
+ ```
422
+
423
+ ### transparentize(value, opacity)
424
+
425
+ Shortcut for a transparent color.
426
+
427
+ ```js
428
+ transparentize("#4e73df", 0.5); // 'rgba(78, 115, 223, 0.5)'
429
+ ```
430
+
431
+ ---
432
+
433
+ ## Script Loader
434
+
435
+ ### loadScripts(scriptsArray, callbackFunc)
436
+
437
+ Loads scripts by URL, then runs the callback — on-demand libraries.
438
+
439
+ ```js
440
+ loadScripts(["/include/pdfmake/pdfmake.min.js", "/include/pdfmake/vfs_fonts.js"], function () {
441
+ printPDF({ data: docDef, download: true });
442
+ });
443
+ ```
444
+
445
+ ---
446
+
447
+ ## Device Detection
448
+
449
+ ### extractHostname(url) / getDomainWithoutSubdomain(url)
450
+
451
+ Extract hostname/domain from a URL. Internal; rarely used directly.
452
+
453
+ ```js
454
+ extractHostname("https://api.fui.vn/fp/test"); // 'api.fui.vn'
455
+ getDomainWithoutSubdomain("https://api.fui.vn"); // 'fui.vn'
456
+ ```