@saltcorn/data 1.6.0-alpha.1 → 1.6.0-alpha.3

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 (230) hide show
  1. package/dist/base-plugin/actions.d.ts +1 -1
  2. package/dist/base-plugin/fileviews.d.ts +3 -0
  3. package/dist/base-plugin/fileviews.js +13 -5
  4. package/dist/base-plugin/fileviews.js.map +1 -1
  5. package/dist/base-plugin/index.d.ts.map +1 -1
  6. package/dist/base-plugin/viewtemplates/edit.js +1 -1
  7. package/dist/base-plugin/viewtemplates/edit.js.map +1 -1
  8. package/dist/base-plugin/viewtemplates/filter.js +1 -1
  9. package/dist/base-plugin/viewtemplates/filter.js.map +1 -1
  10. package/dist/base-plugin/viewtemplates/list.js +1 -1
  11. package/dist/base-plugin/viewtemplates/list.js.map +1 -1
  12. package/dist/base-plugin/viewtemplates/listshowlist.js +1 -1
  13. package/dist/base-plugin/viewtemplates/listshowlist.js.map +1 -1
  14. package/dist/base-plugin/viewtemplates/room.js +1 -1
  15. package/dist/base-plugin/viewtemplates/room.js.map +1 -1
  16. package/dist/base-plugin/viewtemplates/show.js +1 -1
  17. package/dist/base-plugin/viewtemplates/show.js.map +1 -1
  18. package/dist/base-plugin/viewtemplates/viewable_fields.d.ts +2 -187
  19. package/dist/base-plugin/viewtemplates/viewable_fields.d.ts.map +1 -1
  20. package/dist/base-plugin/viewtemplates/viewable_fields.js +2 -1898
  21. package/dist/base-plugin/viewtemplates/viewable_fields.js.map +1 -1
  22. package/dist/base-plugin/viewtemplates/workflow-room.js +1 -1
  23. package/dist/base-plugin/viewtemplates/workflow-room.js.map +1 -1
  24. package/dist/db/connect.d.ts.map +1 -1
  25. package/dist/db/connect.js +19 -2
  26. package/dist/db/connect.js.map +1 -1
  27. package/dist/db/state.d.ts.map +1 -1
  28. package/dist/db/state.js +4 -30
  29. package/dist/db/state.js.map +1 -1
  30. package/dist/diagram/node_extract_utils.js +1 -1
  31. package/dist/diagram/node_extract_utils.js.map +1 -1
  32. package/dist/mobile-mocks/npm/apns2.d.ts +1 -0
  33. package/dist/mobile-mocks/npm/apns2.d.ts.map +1 -0
  34. package/dist/mobile-mocks/npm/apns2.js +2 -0
  35. package/dist/mobile-mocks/npm/apns2.js.map +1 -0
  36. package/dist/mobile-mocks/npm/vm2.d.ts +1 -0
  37. package/dist/mobile-mocks/npm/vm2.d.ts.map +1 -0
  38. package/dist/mobile-mocks/npm/vm2.js +2 -0
  39. package/dist/mobile-mocks/npm/vm2.js.map +1 -0
  40. package/dist/mobile-mocks/npm/xml2js.d.ts +1 -0
  41. package/dist/mobile-mocks/npm/xml2js.d.ts.map +1 -0
  42. package/dist/mobile-mocks/npm/xml2js.js +2 -0
  43. package/dist/mobile-mocks/npm/xml2js.js.map +1 -0
  44. package/dist/models/config.d.ts.map +1 -1
  45. package/dist/models/config.js +27 -0
  46. package/dist/models/config.js.map +1 -1
  47. package/dist/models/file.d.ts +1 -1
  48. package/dist/models/file.d.ts.map +1 -1
  49. package/dist/models/form.d.ts +2 -0
  50. package/dist/models/form.d.ts.map +1 -1
  51. package/dist/models/form.js +1 -0
  52. package/dist/models/form.js.map +1 -1
  53. package/dist/models/internal/push_message_helper.d.ts +63 -24
  54. package/dist/models/internal/push_message_helper.d.ts.map +1 -1
  55. package/dist/models/internal/push_message_helper.js +218 -42
  56. package/dist/models/internal/push_message_helper.js.map +1 -1
  57. package/dist/models/page.d.ts.map +1 -1
  58. package/dist/models/page.js +28 -1
  59. package/dist/models/page.js.map +1 -1
  60. package/dist/models/plugin.d.ts +2 -0
  61. package/dist/models/plugin.d.ts.map +1 -1
  62. package/dist/models/plugin.js +8 -1
  63. package/dist/models/plugin.js.map +1 -1
  64. package/dist/models/scheduler.js +1 -1
  65. package/dist/models/scheduler.js.map +1 -1
  66. package/dist/models/table.d.ts.map +1 -1
  67. package/dist/models/table.js +8 -4
  68. package/dist/models/table.js.map +1 -1
  69. package/dist/models/workflow_step.d.ts +5 -0
  70. package/dist/models/workflow_step.d.ts.map +1 -1
  71. package/dist/models/workflow_step.js +142 -0
  72. package/dist/models/workflow_step.js.map +1 -1
  73. package/dist/plugin-helper.d.ts +1 -1
  74. package/dist/plugin-helper.d.ts.map +1 -1
  75. package/dist/plugin-helper.js +18 -7
  76. package/dist/plugin-helper.js.map +1 -1
  77. package/dist/plugin-testing.js +1 -1
  78. package/dist/plugin-testing.js.map +1 -1
  79. package/dist/utils.d.ts +6 -0
  80. package/dist/utils.d.ts.map +1 -1
  81. package/dist/utils.js +69 -5
  82. package/dist/utils.js.map +1 -1
  83. package/dist/viewable_fields.d.ts +16 -0
  84. package/dist/viewable_fields.d.ts.map +1 -1
  85. package/dist/viewable_fields.js +342 -4
  86. package/dist/viewable_fields.js.map +1 -1
  87. package/dist/web-mobile-commons.js +1 -1
  88. package/dist/web-mobile-commons.js.map +1 -1
  89. package/package.json +12 -9
  90. package/webpack.config.js +3 -0
  91. package/dist/models/s3_helpers.d.ts +0 -54
  92. package/dist/models/s3_helpers.d.ts.map +0 -1
  93. package/dist/models/s3_helpers.js +0 -505
  94. package/dist/models/s3_helpers.js.map +0 -1
  95. package/dist/test-utils/mocks.d.ts +0 -203
  96. package/dist/test-utils/mocks.d.ts.map +0 -1
  97. package/dist/test-utils/mocks.js +0 -329
  98. package/dist/test-utils/mocks.js.map +0 -1
  99. package/dist/tests/actions.test.d.ts +0 -2
  100. package/dist/tests/actions.test.d.ts.map +0 -1
  101. package/dist/tests/actions.test.js +0 -860
  102. package/dist/tests/actions.test.js.map +0 -1
  103. package/dist/tests/auth.test.d.ts +0 -2
  104. package/dist/tests/auth.test.d.ts.map +0 -1
  105. package/dist/tests/auth.test.js +0 -790
  106. package/dist/tests/auth.test.js.map +0 -1
  107. package/dist/tests/auxtest.test.d.ts +0 -2
  108. package/dist/tests/auxtest.test.d.ts.map +0 -1
  109. package/dist/tests/auxtest.test.js +0 -556
  110. package/dist/tests/auxtest.test.js.map +0 -1
  111. package/dist/tests/base.test.d.ts +0 -2
  112. package/dist/tests/base.test.d.ts.map +0 -1
  113. package/dist/tests/base.test.js +0 -30
  114. package/dist/tests/base.test.js.map +0 -1
  115. package/dist/tests/calc.test.d.ts +0 -2
  116. package/dist/tests/calc.test.d.ts.map +0 -1
  117. package/dist/tests/calc.test.js +0 -1081
  118. package/dist/tests/calc.test.js.map +0 -1
  119. package/dist/tests/composite_pk.test.d.ts +0 -2
  120. package/dist/tests/composite_pk.test.d.ts.map +0 -1
  121. package/dist/tests/composite_pk.test.js +0 -98
  122. package/dist/tests/composite_pk.test.js.map +0 -1
  123. package/dist/tests/config.test.d.ts +0 -2
  124. package/dist/tests/config.test.d.ts.map +0 -1
  125. package/dist/tests/config.test.js +0 -86
  126. package/dist/tests/config.test.js.map +0 -1
  127. package/dist/tests/db.test.d.ts +0 -2
  128. package/dist/tests/db.test.d.ts.map +0 -1
  129. package/dist/tests/db.test.js +0 -178
  130. package/dist/tests/db.test.js.map +0 -1
  131. package/dist/tests/discover.test.d.ts +0 -2
  132. package/dist/tests/discover.test.d.ts.map +0 -1
  133. package/dist/tests/discover.test.js +0 -245
  134. package/dist/tests/discover.test.js.map +0 -1
  135. package/dist/tests/edit.test.d.ts +0 -2
  136. package/dist/tests/edit.test.d.ts.map +0 -1
  137. package/dist/tests/edit.test.js +0 -1161
  138. package/dist/tests/edit.test.js.map +0 -1
  139. package/dist/tests/email.test.d.ts +0 -2
  140. package/dist/tests/email.test.d.ts.map +0 -1
  141. package/dist/tests/email.test.js +0 -255
  142. package/dist/tests/email.test.js.map +0 -1
  143. package/dist/tests/exact_views.test.d.ts +0 -2
  144. package/dist/tests/exact_views.test.d.ts.map +0 -1
  145. package/dist/tests/exact_views.test.js +0 -1363
  146. package/dist/tests/exact_views.test.js.map +0 -1
  147. package/dist/tests/field.test.d.ts +0 -2
  148. package/dist/tests/field.test.d.ts.map +0 -1
  149. package/dist/tests/field.test.js +0 -588
  150. package/dist/tests/field.test.js.map +0 -1
  151. package/dist/tests/fieldviews.test.d.ts +0 -2
  152. package/dist/tests/fieldviews.test.d.ts.map +0 -1
  153. package/dist/tests/fieldviews.test.js +0 -74
  154. package/dist/tests/fieldviews.test.js.map +0 -1
  155. package/dist/tests/file.test.d.ts +0 -2
  156. package/dist/tests/file.test.d.ts.map +0 -1
  157. package/dist/tests/file.test.js +0 -148
  158. package/dist/tests/file.test.js.map +0 -1
  159. package/dist/tests/filter.test.d.ts +0 -2
  160. package/dist/tests/filter.test.d.ts.map +0 -1
  161. package/dist/tests/filter.test.js +0 -496
  162. package/dist/tests/filter.test.js.map +0 -1
  163. package/dist/tests/form.test.d.ts +0 -2
  164. package/dist/tests/form.test.d.ts.map +0 -1
  165. package/dist/tests/form.test.js +0 -199
  166. package/dist/tests/form.test.js.map +0 -1
  167. package/dist/tests/list.test.d.ts +0 -2
  168. package/dist/tests/list.test.d.ts.map +0 -1
  169. package/dist/tests/list.test.js +0 -1037
  170. package/dist/tests/list.test.js.map +0 -1
  171. package/dist/tests/mail_queue.test.d.ts +0 -2
  172. package/dist/tests/mail_queue.test.d.ts.map +0 -1
  173. package/dist/tests/mail_queue.test.js +0 -122
  174. package/dist/tests/mail_queue.test.js.map +0 -1
  175. package/dist/tests/models.test.d.ts +0 -2
  176. package/dist/tests/models.test.d.ts.map +0 -1
  177. package/dist/tests/models.test.js +0 -417
  178. package/dist/tests/models.test.js.map +0 -1
  179. package/dist/tests/page.test.d.ts +0 -2
  180. package/dist/tests/page.test.d.ts.map +0 -1
  181. package/dist/tests/page.test.js +0 -26
  182. package/dist/tests/page.test.js.map +0 -1
  183. package/dist/tests/page_group.test.d.ts +0 -2
  184. package/dist/tests/page_group.test.d.ts.map +0 -1
  185. package/dist/tests/page_group.test.js +0 -51
  186. package/dist/tests/page_group.test.js.map +0 -1
  187. package/dist/tests/plugin.test.d.ts +0 -2
  188. package/dist/tests/plugin.test.d.ts.map +0 -1
  189. package/dist/tests/plugin.test.js +0 -60
  190. package/dist/tests/plugin.test.js.map +0 -1
  191. package/dist/tests/show.test.d.ts +0 -2
  192. package/dist/tests/show.test.d.ts.map +0 -1
  193. package/dist/tests/show.test.js +0 -561
  194. package/dist/tests/show.test.js.map +0 -1
  195. package/dist/tests/state.test.d.ts +0 -2
  196. package/dist/tests/state.test.d.ts.map +0 -1
  197. package/dist/tests/state.test.js +0 -82
  198. package/dist/tests/state.test.js.map +0 -1
  199. package/dist/tests/table.test.d.ts +0 -2
  200. package/dist/tests/table.test.d.ts.map +0 -1
  201. package/dist/tests/table.test.js +0 -2717
  202. package/dist/tests/table.test.js.map +0 -1
  203. package/dist/tests/table_history.test.d.ts +0 -2
  204. package/dist/tests/table_history.test.d.ts.map +0 -1
  205. package/dist/tests/table_history.test.js +0 -413
  206. package/dist/tests/table_history.test.js.map +0 -1
  207. package/dist/tests/table_sync_info.test.d.ts +0 -2
  208. package/dist/tests/table_sync_info.test.d.ts.map +0 -1
  209. package/dist/tests/table_sync_info.test.js +0 -62
  210. package/dist/tests/table_sync_info.test.js.map +0 -1
  211. package/dist/tests/tag.test.d.ts +0 -2
  212. package/dist/tests/tag.test.d.ts.map +0 -1
  213. package/dist/tests/tag.test.js +0 -97
  214. package/dist/tests/tag.test.js.map +0 -1
  215. package/dist/tests/user.test.d.ts +0 -2
  216. package/dist/tests/user.test.d.ts.map +0 -1
  217. package/dist/tests/user.test.js +0 -390
  218. package/dist/tests/user.test.js.map +0 -1
  219. package/dist/tests/view.test.d.ts +0 -2
  220. package/dist/tests/view.test.d.ts.map +0 -1
  221. package/dist/tests/view.test.js +0 -699
  222. package/dist/tests/view.test.js.map +0 -1
  223. package/dist/tests/workflow.test.d.ts +0 -2
  224. package/dist/tests/workflow.test.d.ts.map +0 -1
  225. package/dist/tests/workflow.test.js +0 -303
  226. package/dist/tests/workflow.test.js.map +0 -1
  227. package/dist/tests/workflow_run.test.d.ts +0 -2
  228. package/dist/tests/workflow_run.test.d.ts.map +0 -1
  229. package/dist/tests/workflow_run.test.js +0 -826
  230. package/dist/tests/workflow_run.test.js.map +0 -1
@@ -1,1900 +1,4 @@
1
1
  "use strict";
2
- /**
3
- * @category saltcorn-data
4
- * @module base-plugin/viewtemplates/viewable_fields
5
- * @subcategory base-plugin
6
- */
7
- const { post_btn } = require("@saltcorn/markup");
8
- const { text, a, i, div, button, span, script, domReady, input, } = require("@saltcorn/markup/tags");
9
- const { getState, getReq__ } = require("../../db/state");
10
- const { link_view, displayType, run_action_column, stateToQueryString, pathToState, } = require("../../plugin-helper");
11
- const { eval_expression, freeVariables, get_expression_function, } = require("../../models/expression");
12
- const Field = require("../../models/field");
13
- const FieldRepeat = require("../../models/fieldrepeat");
14
- const Form = require("../../models/form");
15
- const { traverseSync, traverse, translateLayout, } = require("../../models/layout");
16
- const { structuredClone, isWeb, isOfflineMode, getSessionId, interpolate, objectToQueryString, validSqlId, InvalidConfiguration, renderServerSide, } = require("../../utils");
17
- const db = require("../../db");
18
- const View = require("../../models/view");
19
- const Table = require("../../models/table");
20
- const { isNode, dollarizeObject, getSafeBaseUrl } = require("../../utils");
21
- const { bool, date } = require("../types");
22
- const _ = require("underscore");
23
- const renderLayout = require("@saltcorn/markup/layout");
24
- const Crash = require("../../models/crash");
25
- const { Relation, parseRelationPath, RelationType, ViewDisplayType, } = require("@saltcorn/common-code");
26
- const { show_icon_and_label } = require("@saltcorn/markup/layout_utils");
27
- /**
28
- * formats the column index of a view cfg
29
- * @param {number|undefined} colIndex
30
- * @returns json formatted attribute for run_action
31
- */
32
- const columnIndex = (colIndex) => colIndex ? `, column_index: ${colIndex}` : "";
33
- /**
34
- * @param {string} viewname
35
- * @param {Table|object} table
36
- * @param {string} action_name
37
- * @param {object} r
38
- * @param {string} colId
39
- * @param {string} colIdNm
40
- * @param {string} confirm
41
- * @param {number|undefined} index
42
- * @returns {any}
43
- */
44
- const action_url = (viewname, table, action_name, r, colId, colIdNm, confirm, colIndex, runAsync) => {
45
- const pk_name = table.pk_name;
46
- const __ = getReq__();
47
- const confirmStr = confirm ? `if(confirm('${__("Are you sure?")}'))` : "";
48
- if (action_name === "Delete") {
49
- return {
50
- javascript: `${confirmStr}${isNode() ? "ajax" : "local"}_post_btn('${!isNode() ? "post" : ""}${table.delete_url(r, `redirect=/view/${viewname}`)}', true)`,
51
- };
52
- }
53
- else if (action_name === "GoBack")
54
- return {
55
- javascript: isNode()
56
- ? "history.back()"
57
- : "parent.saltcorn.mobileApp.navigation.goBack()",
58
- };
59
- else if (action_name.startsWith("Toggle")) {
60
- const field_name = action_name.replace("Toggle ", "");
61
- return `/edit/toggle/${table.name}/${r[pk_name]}/${field_name}?redirect=/view/${viewname}`;
62
- }
63
- return {
64
- javascript: `${confirmStr}view_post('${viewname}', 'run_action', {${colIdNm}:'${colId}'${r ? `, ${pk_name}:'${r?.[pk_name]}'` : ""}${columnIndex(colIndex)}}${runAsync ? `,{runAsync:true}` : ""});`,
65
- };
66
- };
67
- /**
68
- * @param {string} url
69
- * @param {object} req
70
- * @param {object} opts
71
- * @param {string} opts.action_name
72
- * @param {string} opts.action_label
73
- * @param {*} opts.confirm
74
- * @param {*} opts.rndid
75
- * @param {string} opts.action_style
76
- * @param {number} opts.action_size
77
- * @param {*} opts.action_icon
78
- * @param {string} opts.action_bgcol
79
- * @param {string} opts.action_bordercol
80
- * @param {string} opts.action_textcol
81
- * @param {*} __
82
- * @returns {object}
83
- */
84
- const action_link = (url, req, { action_name, action_label, confirm, rndid, action_style, action_size, action_icon, action_bgcol, action_title, action_class, action_bordercol, action_textcol, spinner, block, }, __ = (s) => s) => {
85
- const label = action_label === " " ? "" : __(action_label) || action_name;
86
- let style = action_style === "btn-custom-color"
87
- ? `background-color: ${action_bgcol || "#000000"};border-color: ${action_bordercol || "#000000"}; color: ${action_textcol || "#000000"}`
88
- : null;
89
- if (url.javascript)
90
- return a({
91
- href: "javascript:void(0)",
92
- onclick: `${spinner ? "spin_action_link(this);" : ""}${url.javascript}`,
93
- class: [
94
- action_style === "btn-link"
95
- ? ""
96
- : `btn ${action_style || "btn-primary"} ${action_size || ""}`,
97
- action_class,
98
- ],
99
- style,
100
- title: action_title,
101
- }, action_icon && action_icon !== "empty"
102
- ? i({ class: action_icon }) + (label ? " " : "")
103
- : false, label);
104
- else
105
- return post_btn(url, label, req.csrfToken(), {
106
- confirm,
107
- req,
108
- icon: action_icon,
109
- style,
110
- spinner,
111
- btnClass: `${action_style || "btn-primary"} ${action_size || ""}`,
112
- formClass: !block && "d-inline",
113
- });
114
- };
115
- const slug_transform = (row) => (step) => step.transform === "slugify"
116
- ? `/${db.slugify(row[step.field])}`
117
- : `/${row[step.field]}`;
118
- /**
119
- * @function
120
- * @param {Field[]} fields
121
- * @returns {function}
122
- */
123
- const get_view_link_query = (fields, view) => {
124
- if (view && view.slug && view.slug.steps && view.slug.steps.length > 0) {
125
- return (r) => view.slug.steps.map(slug_transform(r)).join("");
126
- }
127
- const pk_name = fields.find((f) => f.primary_key).name;
128
- return (r) => `?${pk_name}=${r[pk_name]}`;
129
- };
130
- /**
131
- * @function
132
- * @param {object} opts
133
- * @param {string} opts.link_text
134
- * @param {boolean} opts.link_text_formula missing in contract
135
- * @param {string} [opts.link_url]
136
- * @param {boolean} opts.link_url_formula
137
- * @param {boolean} opts.link_target_blank
138
- * @param {Field[]} fields
139
- * @returns {object}
140
- */
141
- const make_link = ({ link_text, link_text_formula, link_url, link_url_formula, link_target_blank, in_dropdown, in_modal, link_icon, icon, link_style, link_size, }, fields, __ = (s) => s, in_row_click) => {
142
- return {
143
- label: "",
144
- key: (r) => {
145
- let txt, href;
146
- const theIcon = link_icon || icon;
147
- txt = link_text_formula
148
- ? eval_expression(link_text, r, undefined, "Link text formula")
149
- : link_text;
150
- href = link_url_formula
151
- ? eval_expression(link_url, r, undefined, "Link URL formula")
152
- : link_url;
153
- const attrs = { href };
154
- if (link_target_blank)
155
- attrs.target = "_blank";
156
- if (in_dropdown)
157
- attrs.class = ["dropdown-item"];
158
- if (link_style)
159
- attrs.class = [
160
- ...(attrs.class || []),
161
- link_style,
162
- link_style.includes("btn") && "d-inline-block",
163
- ];
164
- if (link_size)
165
- attrs.class = [...(attrs.class || []), link_size];
166
- if (in_row_click)
167
- attrs.onclick = "event.stopPropagation()";
168
- if (in_modal)
169
- return a({
170
- ...attrs,
171
- href: "javascript:void(0)",
172
- onclick: isNode()
173
- ? `ajax_modal('${href}');` + (attrs.onclick || "")
174
- : `mobile_modal('${href}');` + (attrs.onclick || ""),
175
- }, !!theIcon && theIcon !== "empty" && i({ class: theIcon }), txt);
176
- return a(attrs, !!theIcon && theIcon !== "empty" && i({ class: theIcon }), txt);
177
- },
178
- };
179
- };
180
- /**
181
- * @param {string} view name of the view or a legacy relation (type:telation)
182
- * @param {string} relation new relation path syntax
183
- * @returns {object}
184
- */
185
- const parse_view_select = (view, relation) => {
186
- if (relation) {
187
- const { sourcetable, path } = relation === Relation.fixedUserRelation
188
- ? { sourcetable: "users", path: [] }
189
- : parseRelationPath(relation);
190
- return {
191
- type: "RelationPath",
192
- viewname: view,
193
- sourcetable,
194
- path,
195
- };
196
- }
197
- else {
198
- // legacy relation path
199
- const colonSplit = view.split(":");
200
- if (colonSplit.length === 1)
201
- return { type: "Own", viewname: view };
202
- const [type, vrest] = colonSplit;
203
- switch (type) {
204
- case "Own":
205
- return { type, viewname: vrest };
206
- case "ChildList":
207
- case "OneToOneShow":
208
- const [viewnm, tbl, fld, throughTable, through] = vrest.split(".");
209
- return {
210
- type,
211
- viewname: viewnm,
212
- table_name: tbl,
213
- field_name: fld,
214
- throughTable,
215
- through,
216
- };
217
- case "ParentShow":
218
- const [pviewnm, ptbl, pfld] = vrest.split(".");
219
- return { type, viewname: pviewnm, table_name: ptbl, field_name: pfld };
220
- case "Independent":
221
- return { type, viewname: vrest };
222
- }
223
- }
224
- };
225
- const pathToQuery = (relation, srcTable, subTable, row) => {
226
- const path = relation.path;
227
- switch (relation.type) {
228
- case RelationType.CHILD_LIST:
229
- return path.length === 1
230
- ? `?${path[0].inboundKey}=${row[srcTable.pk_name]}` // works for OneToOneShow as well
231
- : `?${path[1].table}.${path[1].inboundKey}.${path[0].table}.${path[0].inboundKey}=${row[srcTable.pk_name]}`;
232
- case RelationType.PARENT_SHOW:
233
- const fkey = path[0].fkey;
234
- const reffield = srcTable.fields.find((f) => f.name === fkey);
235
- const value = row[fkey];
236
- return value
237
- ? `?${reffield.refname}=${typeof value === "object" ? value.id : value}`
238
- : null;
239
- case RelationType.OWN:
240
- const getQuery = get_view_link_query(srcTable.fields, relation.subView || {});
241
- return getQuery(row);
242
- case RelationType.INDEPENDENT:
243
- return "";
244
- case RelationType.RELATION_PATH:
245
- const idName = path.length > 0
246
- ? path[0].fkey
247
- ? path[0].fkey
248
- : subTable.pk_name
249
- : undefined;
250
- const srcId = row[idName] === null || row[idName]?.id === null
251
- ? "NULL"
252
- : row[idName]?.id || row[idName];
253
- return `?${relation.relationString}=${srcId}`;
254
- }
255
- };
256
- //todo: use above to simplify code
257
- /**
258
- * @function
259
- * @param {object} opts
260
- * @param {string} opts.view
261
- * @param {string} opts.relation
262
- * @param {object} opts.view_label missing in contract
263
- * @param {object} opts.in_modal
264
- * @param {object} opts.view_label_formula
265
- * @param {string} [opts.link_style = ""]
266
- * @param {string} [opts.link_size = ""]
267
- * @param {string} [opts.link_icon = ""]
268
- * @param {string} [opts.textStyle = ""]
269
- * @param {string} [opts.link_bgcol]
270
- * @param {string} [opts.link_bordercol]
271
- * @param {string} [opts.link_textcol]
272
- * @param {Field[]} fields
273
- * @returns {object}
274
- */
275
- const view_linker = ({ view, relation, view_label, in_modal, view_label_formula, link_style = "", link_size = "", link_icon = "", icon = "", textStyle = "", link_bgcol, link_bordercol, link_textcol, in_dropdown, extra_state_fml, link_target_blank, link_title, link_class, }, fields, __ = (s) => s, isWeb = true, user, targetPrefix = "", state = {}, req, srcViewName, label_attr, //for sorting
276
- in_row_click) => {
277
- const safePrefix = (targetPrefix || "").endsWith("/")
278
- ? targetPrefix.substring(0, targetPrefix.length - 1)
279
- : targetPrefix || "";
280
- const get_label = (def, row) => {
281
- if (!view_label || view_label.length === 0)
282
- return def;
283
- if (!view_label_formula)
284
- return __(view_label);
285
- return eval_expression(view_label, row, user, "View Link label formula");
286
- };
287
- const get_extra_state = (row) => {
288
- if (!extra_state_fml)
289
- return "";
290
- const ctx = {
291
- ...dollarizeObject(state),
292
- session_id: getSessionId(req),
293
- ...row,
294
- };
295
- const o = eval_expression(extra_state_fml, ctx, user, "View link extra state formula");
296
- return Object.entries(o)
297
- .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
298
- .join("&");
299
- };
300
- if (relation) {
301
- const topview = View.findOne({ name: srcViewName });
302
- const srcTable = Table.findOne({ id: topview.table_id });
303
- const subview = View.findOne({ name: view });
304
- const subTable = Table.findOne({ id: subview.table_id });
305
- const relObj = new Relation(relation, subTable ? subTable.name : "", ViewDisplayType.NO_ROW_LIMIT);
306
- relObj.subView = subview;
307
- const type = relObj.type;
308
- return {
309
- label: view,
310
- key: (r) => {
311
- const query = pathToQuery(relObj, srcTable, subTable, r);
312
- if (query === null)
313
- return "";
314
- else {
315
- let label = "";
316
- if (type === "ParentShow") {
317
- const summary_field = r[`summary_field_${subTable.name.toLowerCase()}`];
318
- label = get_label(typeof summary_field === "undefined" ? view : summary_field, r);
319
- }
320
- else
321
- label = get_label(view, r);
322
- const target = `${safePrefix}/view/${encodeURIComponent(view)}${query}`;
323
- return link_view(isWeb || in_modal ? target : `javascript:execLink('${target}')`, label, in_modal && srcViewName && { reload_view: srcViewName }, link_style, link_size, link_icon || icon, textStyle, link_bgcol, link_bordercol, link_textcol, in_dropdown && "dropdown-item", get_extra_state(r), link_target_blank, label_attr, link_title, link_class, req, in_row_click);
324
- }
325
- },
326
- };
327
- }
328
- else {
329
- // legacy relation path
330
- const [vtype, vrest] = view.split(":");
331
- switch (vtype) {
332
- case "Own":
333
- const vnm = vrest;
334
- const viewrow = View.findOne({ name: vnm });
335
- const get_query = get_view_link_query(fields, viewrow || {});
336
- return {
337
- label: vnm,
338
- key: (r) => {
339
- const target = `${safePrefix}/view/${encodeURIComponent(vnm)}${get_query(r)}`;
340
- return link_view(isWeb || in_modal ? target : `javascript:execLink('${target}')`, get_label(vnm, r), in_modal && srcViewName && { reload_view: srcViewName }, link_style, link_size, link_icon || icon, textStyle, link_bgcol, link_bordercol, link_textcol, in_dropdown && "dropdown-item", get_extra_state(r), link_target_blank, label_attr, link_title, link_class, req, in_row_click);
341
- },
342
- };
343
- case "Independent":
344
- const ivnm = vrest;
345
- return {
346
- label: ivnm,
347
- key: (r) => {
348
- const target = `${safePrefix}/view/${encodeURIComponent(ivnm)}`;
349
- return link_view(isWeb || in_modal ? target : `javascript:execLink('${target}')`, get_label(ivnm, r), in_modal && srcViewName && { reload_view: srcViewName }, link_style, link_size, link_icon || icon, textStyle, link_bgcol, link_bordercol, link_textcol, in_dropdown && "dropdown-item", get_extra_state(r), link_target_blank, label_attr, link_title, link_class, req, in_row_click);
350
- },
351
- };
352
- case "ChildList":
353
- case "OneToOneShow":
354
- const [viewnm, tbl, fld, throughTable, through] = vrest.split(".");
355
- const varPath = through ? `${throughTable}.${through}.${fld}` : fld;
356
- return {
357
- label: viewnm,
358
- key: (r) => {
359
- const target = `${safePrefix}/view/${encodeURIComponent(viewnm)}?${varPath}=${r.id}`;
360
- return link_view(isWeb || in_modal ? target : `javascript:execLink('${target}')`, get_label(viewnm, r), in_modal && srcViewName && { reload_view: srcViewName }, link_style, link_size, link_icon || icon, textStyle, link_bgcol, link_bordercol, link_textcol, in_dropdown && "dropdown-item", get_extra_state(r), link_target_blank, label_attr, link_title, link_class, req, in_row_click);
361
- },
362
- };
363
- case "ParentShow":
364
- const [pviewnm, ptbl, pfld] = vrest.split(".");
365
- //console.log([pviewnm, ptbl, pfld])
366
- return {
367
- label: pviewnm,
368
- key: (r) => {
369
- const reffield = fields.find((f) => f.name === pfld);
370
- const summary_field = r[`summary_field_${ptbl.toLowerCase()}`];
371
- if (r[pfld]) {
372
- const target = `${safePrefix}/view/${encodeURIComponent(pviewnm)}?${reffield.refname}=${typeof r[pfld] === "object" ? r[pfld].id : r[pfld]}`;
373
- return link_view(isWeb || in_modal ? target : `javascript:execLink('${target}')`, get_label(typeof summary_field === "undefined"
374
- ? pviewnm
375
- : summary_field, r), in_modal && srcViewName && { reload_view: srcViewName }, link_style, link_size, link_icon || icon, textStyle, link_bgcol, link_bordercol, link_textcol, in_dropdown && "dropdown-item", get_extra_state(r), link_target_blank, label_attr, link_title, link_class, req, in_row_click);
376
- }
377
- else
378
- return "";
379
- },
380
- };
381
- default:
382
- throw new Error("Invalid relation: " + view);
383
- }
384
- }
385
- };
386
- /**
387
- * @param {string} nm
388
- * @returns {boolean}
389
- */
390
- const action_requires_write = (nm) => {
391
- if (!nm)
392
- return false;
393
- if (nm === "Delete")
394
- return true;
395
- if (nm.startsWith("Toggle"))
396
- return true;
397
- };
398
- // flapMap if f returns array
399
- const flapMapish = (xs, f) => {
400
- const res = [];
401
- let index = 0;
402
- for (const x of xs) {
403
- const y = f(x, index++);
404
- if (Array.isArray(y))
405
- res.push(...y);
406
- else
407
- res.push(y);
408
- }
409
- return res;
410
- };
411
- const get_viewable_fields_from_layout = (viewname, statehash, table, fields, columns, isShow, req, __, state = {}, srcViewName, layoutCols, viewResults, in_row_click) => {
412
- const typeMap = {
413
- field: "Field",
414
- join_field: "JoinField",
415
- view_link: "ViewLink",
416
- view: "View",
417
- link: "Link",
418
- action: "Action",
419
- blank: "Text",
420
- aggregation: "Aggregation",
421
- dropdown_menu: "DropdownMenu",
422
- container: "Container",
423
- };
424
- const toArray = (x) => !x ? [] : Array.isArray(x) ? x : x.above ? x.above : [x];
425
- //console.log("layout cols", layoutCols);
426
- const newCols = layoutCols.map(({ contents, ...rest }) => {
427
- if (!contents)
428
- contents = rest;
429
- if (contents.above) {
430
- const newContents = { type: "container", contents: contents };
431
- contents = newContents;
432
- }
433
- const col = {
434
- ...contents,
435
- ...rest,
436
- type: typeMap[contents.type] || contents.type,
437
- };
438
- switch (contents.type) {
439
- case "link":
440
- col.link_text = contents.text;
441
- col.link_url = contents.url;
442
- col.link_url_formula = contents.isFormula?.url;
443
- col.link_text_formula = contents.isFormula?.text;
444
- col.link_target_blank = contents.target_blank;
445
- break;
446
- case "view_link":
447
- col.view_label_formula = contents.isFormula?.label;
448
- break;
449
- case "dropdown_menu":
450
- col.dropdown_columns = get_viewable_fields_from_layout(viewname, statehash, table, fields, columns, isShow, req, __, state, srcViewName, toArray(contents.contents));
451
- break;
452
- case "blank":
453
- if (contents.isFormula?.text) {
454
- col.type = "FormulaValue";
455
- col.formula = col.contents;
456
- }
457
- if (contents.isHTML)
458
- col.interpolator = (row) => interpolate(contents.contents, row, req?.user, "HTML element");
459
- break;
460
- case "action":
461
- col.action_label_formula = contents.isFormula?.action_label;
462
- break;
463
- }
464
- return col;
465
- });
466
- //console.log("newCols", newCols);
467
- return get_viewable_fields(viewname, statehash, table, fields, newCols, isShow, req, __, state, srcViewName, viewResults, in_row_click);
468
- };
469
- /**
470
- * @function
471
- * @param {string} viewname
472
- * @param {Table|object} table
473
- * @param {Field[]} fields
474
- * @param {object[]} columns
475
- * @param {boolean} isShow
476
- * @param {object} req
477
- * @param {*} __
478
- * @returns {object[]}
479
- */
480
- const get_viewable_fields = (viewname, statehash, table, fields, columns, isShow, req, __, state = {}, srcViewName, viewResults, in_row_click) => {
481
- const dropdown_actions = [];
482
- const checkShowIf = (tFieldGenF) => (column, index) => {
483
- const tfield = tFieldGenF(column, index);
484
- if (column.showif) {
485
- const oldKeyF = tfield.key;
486
- if (typeof oldKeyF !== "function")
487
- return tfield;
488
- const newKeyF = (r) => {
489
- if (eval_expression(column.showif, r, req.user, "Column show if formula"))
490
- return oldKeyF(r);
491
- else
492
- return "";
493
- };
494
- tfield.key = newKeyF;
495
- }
496
- return tfield;
497
- };
498
- const tfields = flapMapish(columns, checkShowIf((column, index) => {
499
- const role = req.user ? req.user.role_id : 100;
500
- const user_id = req.user ? req.user.id : null;
501
- const setWidth = column.col_width
502
- ? { width: `${column.col_width}${column.col_width_units}` }
503
- : {};
504
- setWidth.align =
505
- !column.alignment || column.alignment === "Default"
506
- ? undefined
507
- : column.alignment.toLowerCase();
508
- if (column.type === "FormulaValue") {
509
- return {
510
- ...setWidth,
511
- label: column.header_label ? __(column.header_label) : "",
512
- key: (r) => text(eval_expression(column.formula, r, req.user, "Formula value column")),
513
- };
514
- }
515
- else if (column.type === "Text") {
516
- return {
517
- ...setWidth,
518
- label: column.header_label ? __(column.header_label) : "",
519
- key: (r) => column.interpolator
520
- ? column.interpolator(r)
521
- : text(column.contents),
522
- };
523
- }
524
- else if (column.type === "Container") {
525
- return {
526
- ...setWidth,
527
- label: column.header_label ? __(column.header_label) : "",
528
- key: (r) => {
529
- const layout = structuredClone({ ...column, type: "container" });
530
- traverseSync(layout, standardLayoutRowVisitor(viewname, state, table, r, req));
531
- return renderLayout({
532
- blockDispatch: {
533
- ...standardBlockDispatch(viewname, state, table, { req }, r),
534
- view(column) {
535
- return viewResults[column.view + column.relation]?.(r);
536
- },
537
- },
538
- layout,
539
- role,
540
- is_owner: false,
541
- req,
542
- hints: getState().getLayout(req.user).hints || {},
543
- });
544
- },
545
- };
546
- }
547
- else if (column.type === "DropdownMenu") {
548
- //console.log(column);
549
- const btn_label = column.label == " "
550
- ? ""
551
- : column.label || (column.action_icon ? "" : req.__("Action"));
552
- return {
553
- ...setWidth,
554
- label: column.header_label ? __(column.header_label) : "",
555
- key: (r) => div({ class: "dropdown" }, button({
556
- class: column.action_style === "btn-link"
557
- ? "btn btn-link"
558
- : `btn ${column.action_style || "btn-primary"} ${column.action_size || ""} d-inline-block dropdown-toggle`,
559
- "data-boundary": "viewport",
560
- type: "button",
561
- id: `actiondd${r.id}_${index}`, //TODO need unique
562
- "data-bs-toggle": "dropdown",
563
- "aria-haspopup": "true",
564
- "aria-expanded": "false",
565
- "aria-label": "Additional actions",
566
- onclick: in_row_click ? "event.stopPropagation()" : undefined,
567
- style: column.action_style === "btn-custom-color"
568
- ? `background-color: ${column.action_bgcol || "#000000"};border-color: ${column.action_bordercol || "#000000"}; color: ${column.action_textcol || "#000000"}`
569
- : null,
570
- }, show_icon_and_label(column.action_icon, btn_label)), div({
571
- class: [
572
- "dropdown-menu",
573
- column.menu_direction === "end" && "dropdown-menu-end",
574
- ],
575
- "aria-labelledby": `actiondd${r.id}_${index}`,
576
- }, column.dropdown_columns.map((acol) => div({ class: "dropdown-item" }, acol.key(r))))),
577
- };
578
- }
579
- else if (column.type === "Action") {
580
- if (column.minRole && column.minRole != 100) {
581
- const minRole = +column.minRole;
582
- const userRole = req?.user?.role_id || 100;
583
- if (minRole < userRole)
584
- return false;
585
- }
586
- const action_col = {
587
- ...setWidth,
588
- label: column.header_label ? __(column.header_label) : "",
589
- key: (r) => {
590
- if (action_requires_write(column.action_name)) {
591
- if (table.min_role_write < role && !table.is_owner(req.user, r))
592
- return "";
593
- }
594
- const url = action_url(viewname, table, column.action_name, r, column.rndid || column.action_name, column.rndid ? "rndid" : "action_name", column.confirm, index, column.run_async);
595
- const label = column.action_label_formula
596
- ? eval_expression(column.action_label, r, req.user, "Action label formula")
597
- : __(column.action_label) || __(column.action_name);
598
- const icon = column.action_icon || column.icon || undefined;
599
- if (url.javascript)
600
- return a({
601
- href: "javascript:void(0)",
602
- class: [
603
- column.in_dropdown && "dropdown-item",
604
- column.action_style !== "btn-link" &&
605
- `btn ${column.action_style || "btn-primary"} ${column.action_size || ""}`,
606
- ],
607
- onclick: url.javascript +
608
- (column.spinner ? ";spin_action_link(this)" : "") +
609
- (in_row_click ? ";event.stopPropagation()" : ""),
610
- ...(!label || label === " "
611
- ? { "aria-label": column.action_name }
612
- : {}),
613
- }, !!icon &&
614
- icon !== "empty" &&
615
- i({ class: icon }) + (label === " " ? "" : "&nbsp;"), label);
616
- else
617
- return post_btn(url, label, req.csrfToken(), {
618
- small: true,
619
- ajax: true,
620
- icon,
621
- reload_on_done: true,
622
- confirm: column.confirm,
623
- spinner: column.spinner,
624
- btnClass: column.in_dropdown
625
- ? "dropdown-item"
626
- : column.action_style || "btn-primary",
627
- req,
628
- });
629
- },
630
- };
631
- if (column.in_dropdown) {
632
- //legacy
633
- dropdown_actions.push(action_col);
634
- return false;
635
- }
636
- else
637
- return action_col;
638
- }
639
- else if (column.type === "View") {
640
- return {
641
- label: column.header_label ? __(column.header_label) : "",
642
- key: (r) => viewResults[column.view + column.relation]?.(r),
643
- };
644
- }
645
- else if (column.type === "ViewLink") {
646
- if (!column.view)
647
- return;
648
- const r = view_linker(column, fields, __, isWeb(req), req.user, "", state, req, srcViewName, undefined, in_row_click);
649
- //console.log(column);
650
- if (column.view_label_formula) {
651
- const fml_field = table.getField(column.view_label);
652
- if (fml_field) {
653
- if (column.view_label.includes(".")) {
654
- const path = column.view_label.split(".");
655
- if (path.length === 2) {
656
- const [refNm, targetNm] = path;
657
- r.statekey = `${refNm}.${table.getField(refNm).reftable_name}->${targetNm}`;
658
- r.header_filter = headerFilterForField(fml_field, state, r.statekey);
659
- }
660
- }
661
- else {
662
- r.header_filter = headerFilterForField(fml_field, state);
663
- r.statekey = fml_field.name;
664
- }
665
- }
666
- }
667
- if (column.header_label)
668
- r.label = __(column.header_label);
669
- Object.assign(r, setWidth);
670
- if (column.in_dropdown) {
671
- dropdown_actions.push(r);
672
- return false;
673
- }
674
- else
675
- return r;
676
- }
677
- else if (column.type === "Link") {
678
- const r = make_link(column, fields, __, in_row_click);
679
- if (column.header_label)
680
- r.label = __(column.header_label);
681
- Object.assign(r, setWidth);
682
- if (column.in_dropdown) {
683
- dropdown_actions.push(r);
684
- return false;
685
- }
686
- else
687
- return r;
688
- }
689
- else if (column.type === "JoinField") {
690
- //console.log(column);
691
- let fvrun;
692
- const fieldview = column.join_fieldview || column.fieldview;
693
- let refNm, targetNm, through, key, type;
694
- const keypath = column.join_field.split(".");
695
- if (column.join_field.includes("->")) {
696
- const [relation, target] = column.join_field.split("->");
697
- const [ontable, ref] = relation.split(".");
698
- targetNm = target;
699
- refNm = ref;
700
- key = validSqlId(column.targetNm ||
701
- `${ref}_${ontable.replaceAll(" ", "").toLowerCase()}_${target}`);
702
- }
703
- else {
704
- refNm = keypath[0];
705
- targetNm = keypath[keypath.length - 1];
706
- key = keypath.join("_");
707
- }
708
- const field = table.getField(column.join_field);
709
- if (column.field_type)
710
- type = getState().types[column.field_type];
711
- else {
712
- if (field && field.type === "File")
713
- column.field_type = "File";
714
- else if (field?.type.name && field?.type?.fieldviews[fieldview]) {
715
- column.field_type = field.type.name;
716
- type = getState().types[column.field_type];
717
- }
718
- }
719
- if (fieldview && type?.fieldviews?.[fieldview]?.expandColumns) {
720
- return type.fieldviews[fieldview].expandColumns(field, {
721
- ...field.attributes,
722
- ...column,
723
- }, column);
724
- }
725
- let header_filter;
726
- let statekey;
727
- if (!column.join_field.includes("->") && keypath.length == 2) {
728
- statekey = `${refNm}.${table.getField(refNm).reftable_name}->${targetNm}`;
729
- header_filter = headerFilterForField(field, state, statekey);
730
- }
731
- let gofv = fieldview && type && type.fieldviews && type.fieldviews[fieldview]
732
- ? (row) => type.fieldviews[fieldview].run(row[key], req, {
733
- row,
734
- ...(field?.attributes || {}),
735
- ...column,
736
- ...(column?.configuration || {}),
737
- })
738
- : null;
739
- if (!gofv && column.field_type === "File") {
740
- gofv = (row) => row[key]
741
- ? getState().fileviews[fieldview].run(row[key], "", {
742
- row,
743
- ...column,
744
- ...(column?.configuration || {}),
745
- })
746
- : "";
747
- }
748
- fvrun = {
749
- ...setWidth,
750
- label: headerLabelForName(column.header_label ? __(column.header_label) : targetNm, key, req, __, statehash),
751
- row_key: key,
752
- row_label: field?.label,
753
- statekey,
754
- header_filter,
755
- key: gofv ? gofv : (row) => text(row[key]),
756
- sortlink: sortlinkForName(key, req, viewname, statehash),
757
- };
758
- if (column.click_to_edit) {
759
- const reffield = fields.find((f) => f.name === refNm);
760
- const oldkey = typeof fvrun.key === "function" ? fvrun.key : (r) => r[fvrun.key];
761
- const newkey = (row) => div({
762
- "data-inline-edit-fielddata": encodeURIComponent(JSON.stringify({
763
- field_name: keypath[0],
764
- table_name: table.name,
765
- pk: row[table.pk_name],
766
- fieldview,
767
- configuration: column?.configuration,
768
- join_field: keypath[keypath.length - 1],
769
- })),
770
- "data-inline-edit-ajax": "true",
771
- "data-inline-edit-dest-url": `/api/${table.name}/${row[table.pk_name]}`,
772
- }, span({ class: "current" }, oldkey(row)), i({ class: "editicon fas fa-edit ms-1" }));
773
- fvrun.key = newkey;
774
- }
775
- return fvrun;
776
- }
777
- else if (column.type === "Aggregation") {
778
- let table, fld, through;
779
- if (column.agg_relation.includes("->")) {
780
- let restpath;
781
- [through, restpath] = column.agg_relation.split("->");
782
- [table, fld] = restpath.split(".");
783
- }
784
- else {
785
- [table, fld] = column.agg_relation.split(".");
786
- }
787
- let targetNm = column.targetNm ||
788
- db.sqlsanitize((column.stat.replace(" ", "") +
789
- "_" +
790
- table +
791
- "_" +
792
- fld +
793
- "_" +
794
- column.agg_field.split("@")[0] +
795
- "_" +
796
- column.aggwhere || "").toLowerCase());
797
- if (targetNm.length > 58) {
798
- targetNm = targetNm
799
- .split("")
800
- .filter((c, i) => i % 2 == 0)
801
- .join("");
802
- }
803
- let showValue = (value) => {
804
- if (value === true || value === false)
805
- return bool.fieldviews.show.run(value);
806
- if (value instanceof Date)
807
- return date.fieldviews.show.run(value);
808
- return value?.toString ? value.toString() : value;
809
- };
810
- if (column.agg_fieldview && column.agg_field?.includes("@")) {
811
- const tname = column.agg_field.split("@")[1];
812
- const type = getState().types[tname];
813
- if (type?.fieldviews[column.agg_fieldview])
814
- showValue = (x) => type.fieldviews[column.agg_fieldview].run(x, req, column);
815
- }
816
- else if (column.agg_fieldview) {
817
- const aggField = Table.findOne(table)?.getField?.(column.agg_field);
818
- const outcomeType = column.stat === "Percent true" || column.stat === "Percent false"
819
- ? "Float"
820
- : column.stat === "Count" || column.stat === "CountUnique"
821
- ? "Integer"
822
- : aggField?.type?.name;
823
- const type = getState().types[outcomeType];
824
- if (type?.fieldviews[column.agg_fieldview])
825
- showValue = (x) => type.fieldviews[column.agg_fieldview].run(type.read(x), req, {
826
- ...column,
827
- ...(column?.configuration || {}),
828
- });
829
- }
830
- let key = (r) => {
831
- const value = r[targetNm];
832
- return showValue(value);
833
- };
834
- if (column.stat.toLowerCase() === "array_agg")
835
- key = (r) => Array.isArray(r[targetNm])
836
- ? r[targetNm].map((v) => showValue(v)).join(", ")
837
- : "";
838
- return {
839
- ...setWidth,
840
- label: headerLabelForName(column.header_label
841
- ? column.header_label
842
- : column.stat + " " + table, targetNm, req, __, statehash),
843
- key,
844
- sortlink: sortlinkForName(targetNm, req, viewname, statehash),
845
- };
846
- }
847
- else if (column.type === "Field") {
848
- //console.log(column);
849
- let f = fields.find((fld) => fld.name === column.field_name);
850
- let f_with_val = f;
851
- if (f && f.attributes && f.attributes.localized_by) {
852
- const locale = req?.getLocale?.();
853
- const localized_fld_nm = f.attributes.localized_by[locale];
854
- f_with_val = fields.find((fld) => fld.name === localized_fld_nm) || f;
855
- }
856
- const isNum = f && f.type && f.type.name === "Integer";
857
- if (isNum && !setWidth.align)
858
- setWidth.align = "right";
859
- let fvrun;
860
- let header_filter = headerFilterForField(f, state);
861
- if (column.fieldview &&
862
- f?.type?.fieldviews?.[column.fieldview]?.expandColumns) {
863
- fvrun = f.type.fieldviews[column.fieldview].expandColumns(f, {
864
- ...f.attributes,
865
- ...column.configuration,
866
- }, column);
867
- }
868
- else
869
- fvrun = f && {
870
- ...setWidth,
871
- label: headerLabelForName(column.header_label ? __(column.header_label) : __(f.label), f.name, req, __, statehash),
872
- row_key: f_with_val.name,
873
- row_label: f.label,
874
- key: column.fieldview && f.type === "File"
875
- ? (row) => row[f.name] &&
876
- getState().fileviews[column.fieldview].run(row[f.name], row[`${f.name}__filename`], { row, ...column, ...(column?.configuration || {}) })
877
- : column.fieldview &&
878
- f.type.fieldviews &&
879
- f.type.fieldviews[column.fieldview]
880
- ? (row) => f.type.fieldviews[column.fieldview].run(row[f_with_val.name], req, { row, ...f.attributes, ...column.configuration })
881
- : isShow
882
- ? f.type.showAs
883
- ? (row) => f.type.showAs(row[f_with_val.name])
884
- : (row) => text(row[f_with_val.name])
885
- : f.listKey,
886
- header_filter,
887
- sortlink: !f.calculated || f.stored
888
- ? sortlinkForName(f.name, req, viewname, statehash)
889
- : undefined,
890
- };
891
- if (column.click_to_edit) {
892
- const updateKey = (fvr, column_key) => {
893
- const oldkey = typeof fvr.key === "function" ? fvr.key : (r) => r[fvr.key];
894
- const doSetKey = (column.fieldview === "subfield" ||
895
- column.fieldview === "keys_expand_columns") &&
896
- column_key;
897
- const schema = doSetKey && f.attributes?.hasSchema
898
- ? (f.attributes.schema || []).find((s) => s.key === column_key)
899
- : undefined;
900
- const newkey = (row) => {
901
- if (role <= table.min_role_write || table.is_owner(req.user, row))
902
- return div({
903
- "data-inline-edit-fielddata": encodeURIComponent(JSON.stringify({
904
- field_name: f.name,
905
- table_name: table.name,
906
- pk: row[table.pk_name],
907
- fieldview: column.fieldview,
908
- configuration: column?.configuration,
909
- })),
910
- "data-inline-edit-ajax": "true",
911
- "data-inline-edit-dest-url": `/api/${table.name}/${row[table.pk_name]}`,
912
- }, span({ class: "current" }, oldkey(row)), i({ class: "editicon fas fa-edit ms-1" }));
913
- else
914
- return oldkey(row);
915
- };
916
- fvr.key = newkey;
917
- };
918
- if (Array.isArray(fvrun)) {
919
- fvrun.forEach((fvr) => {
920
- updateKey(fvr, fvr.row_key[1]);
921
- });
922
- }
923
- else
924
- updateKey(fvrun, column.configuration?.key || column.key);
925
- }
926
- return fvrun;
927
- }
928
- })).filter((v) => !!v);
929
- if (dropdown_actions.length > 0) {
930
- //legacy
931
- tfields.push({
932
- label: req.__("Action"),
933
- key: (r) => div({ class: "dropdown" }, button({
934
- class: "btn btn-sm btn-xs btn-outline-secondary dropdown-toggle",
935
- "data-boundary": "viewport",
936
- type: "button",
937
- id: `actiondd${r.id}`,
938
- "data-bs-toggle": "dropdown",
939
- "aria-haspopup": "true",
940
- "aria-expanded": "false",
941
- }, req.__("Action")), div({
942
- class: "dropdown-menu dropdown-menu-end",
943
- "aria-labelledby": `actiondd${r.id}`,
944
- }, dropdown_actions.map((acol) => acol.key(r)))),
945
- });
946
- }
947
- return tfields;
948
- };
949
- const headerFilterForField = (f, state, path) => (id) => {
950
- if (f?.type?.name === "Date") {
951
- const set_initial = state[`_fromdate_${f.name}`] && state[`_todate_${f.name}`]
952
- ? `defaultDate: ["${state[`_fromdate_${f.name}`]}", "${state[`_todate_${f.name}`]}"],`
953
- : "";
954
- return (div({ class: "input-group hdrfilterdate" }, input({
955
- type: "text",
956
- class: "form-control",
957
- id: `daterangefilter${f.name}`,
958
- //placeholder: ,
959
- }), button({
960
- class: "btn btn-outline-secondary btn-border-color-input",
961
- style: { paddingLeft: "3px", paddingRight: "3px" },
962
- onclick: `set_state_fields({_fromdate_${f.name}: {unset: true}, _todate_${f.name}: {unset: true} })`,
963
- }, i({ class: "fas fa-times" }))) +
964
- script(domReady(`ensure_script_loaded("/static_assets/${db.connectObj.version_tag}/flatpickr.min.js");
965
- ensure_css_loaded("/static_assets/${db.connectObj.version_tag}/flatpickr.min.css");
966
- $('#daterangefilter${f.name}').flatpickr({mode:'range',
967
- dateFormat: "Y-m-d",${set_initial}
968
- onChange: function(selectedDates, dateStr, instance) {
969
- set_header_filter($(instance.element));
970
- if(selectedDates.length==2) {
971
-
972
- set_state_fields({_fromdate_${f.name}: selectedDates[0].toLocaleDateString('en-CA'), _todate_${f.name}: selectedDates[1].toLocaleDateString('en-CA') }, false, ${id ? `document.getElementById('${id}')` : "this"})
973
-
974
-
975
- }
976
- },
977
- });`)));
978
- }
979
- let fieldviewObjs;
980
- /*if (f.is_fkey) {
981
- fieldviewObjs = [getState().keyFieldviews.select];
982
- } else */
983
- let extraAttrs = {};
984
- if (f?.type?.name === "Bool") {
985
- fieldviewObjs = [f.type.fieldviews.tristate];
986
- extraAttrs.outline_buttons = true;
987
- }
988
- else if (f?.type?.name === "String")
989
- fieldviewObjs = [f.type.fieldviews.edit];
990
- else if (f?.type?.name === "Integer" || f?.type?.name === "Float")
991
- fieldviewObjs = [
992
- f.type.fieldviews.above_input,
993
- f.type.fieldviews.below_input,
994
- ];
995
- if (!fieldviewObjs)
996
- return "";
997
- return div({ class: "d-flex" }, fieldviewObjs
998
- .map((fvObj) => fvObj?.run(f.name, state[path || f.name], {
999
- preOnChange: `set_header_filter(this);`,
1000
- onChange: `set_header_filter(this);set_state_field('${encodeURIComponent(path || f.name)}', this.value, ${id ? `document.getElementById('${id}')` : "this"})`,
1001
- isFilter: true,
1002
- ...f.attributes,
1003
- ...extraAttrs,
1004
- }, "", false, f, state) || "")
1005
- .join(""));
1006
- };
1007
- /**
1008
- * @param {string} fname
1009
- * @param {object} req
1010
- * @returns {string}
1011
- */
1012
- const sortlinkForName = (fname, req, viewname, statehash) => {
1013
- const _sortby = req.query ? req.query[`_${statehash}_sortby`] : undefined;
1014
- const _sortdesc = req.query ? req.query[`_${statehash}_sortdesc`] : undefined;
1015
- const desc = typeof _sortdesc == "undefined"
1016
- ? _sortby === fname
1017
- : _sortdesc
1018
- ? "false"
1019
- : "true";
1020
- return `sortby('${text(fname)}', ${desc}, '${statehash}', this)`;
1021
- };
1022
- const standardLayoutRowVisitor = (viewname, state, table, row, req) => {
1023
- const session_id = getSessionId(req);
1024
- const locale = req.getLocale();
1025
- const fields = table.fields;
1026
- const evalMaybeExpr = (segment, key, fmlkey) => {
1027
- if (segment.isFormula && segment.isFormula[fmlkey || key] && segment[key]) {
1028
- segment[key] = eval_expression(segment[key], { session_id, locale, ...row }, req.user, `property ${key} in segment of type ${segment.type}`);
1029
- }
1030
- };
1031
- return {
1032
- link(segment) {
1033
- evalMaybeExpr(segment, "url");
1034
- evalMaybeExpr(segment, "text");
1035
- if (req?.generate_email &&
1036
- req.get_base_url &&
1037
- segment.url.startsWith("/")) {
1038
- const targetPrefix = req.get_base_url();
1039
- const safePrefix = (targetPrefix || "").endsWith("/")
1040
- ? targetPrefix.substring(0, targetPrefix.length - 1)
1041
- : targetPrefix || "";
1042
- segment.url = safePrefix + segment.url;
1043
- }
1044
- },
1045
- view_link(segment) {
1046
- evalMaybeExpr(segment, "view_label", "label");
1047
- },
1048
- blank(segment) {
1049
- evalMaybeExpr(segment, "contents", "text");
1050
- },
1051
- tabs(segment) {
1052
- const to_delete = new Set();
1053
- (segment.showif || []).forEach((sif, ix) => {
1054
- if (sif) {
1055
- const showit = eval_expression(sif, { session_id, ...row }, req.user, `Tabs show if formula`);
1056
- if (!showit)
1057
- to_delete.add(ix);
1058
- }
1059
- });
1060
- // TODO mutation here - potential issue with renderRows
1061
- segment.titles = segment.titles.filter((v, ix) => !to_delete.has(ix));
1062
- segment.contents = segment.contents.filter((v, ix) => !to_delete.has(ix));
1063
- (segment.titles || []).forEach((t, ix) => {
1064
- if (typeof t === "string" && t.includes("{{")) {
1065
- segment.titles[ix] = interpolate(t, row, req.user, "Tab titles");
1066
- }
1067
- });
1068
- },
1069
- action(segment) {
1070
- evalMaybeExpr(segment, "action_label");
1071
- },
1072
- card(segment) {
1073
- evalMaybeExpr(segment, "url");
1074
- evalMaybeExpr(segment, "title");
1075
- evalMaybeExpr(segment, "class");
1076
- },
1077
- image(segment) {
1078
- evalMaybeExpr(segment, "url");
1079
- evalMaybeExpr(segment, "alt");
1080
- if (segment.srctype === "Field") {
1081
- const field = fields.find((f) => f.name === segment.field);
1082
- if (!field)
1083
- return;
1084
- if (field.type.name === "String")
1085
- segment.url = row[segment.field];
1086
- if (field.type === "File") {
1087
- segment.url = `/files/serve/${row[segment.field]}`;
1088
- segment.fileid = row[segment.field];
1089
- }
1090
- }
1091
- },
1092
- container(segment) {
1093
- evalMaybeExpr(segment, "bgColor");
1094
- evalMaybeExpr(segment, "customClass");
1095
- evalMaybeExpr(segment, "customId");
1096
- evalMaybeExpr(segment, "url");
1097
- if (segment.bgType === "Image Field") {
1098
- segment.bgType = "Image";
1099
- segment.bgFileId = row[segment.bgField];
1100
- }
1101
- if (segment.showIfFormula) {
1102
- const f = get_expression_function(segment.showIfFormula, fields);
1103
- if (!f({ ...dollarizeObject(state || {}), ...row }, req.user))
1104
- segment.hide = true;
1105
- else
1106
- segment.hide = false;
1107
- }
1108
- if (segment.click_action) {
1109
- segment.url = `javascript:view_post('${viewname}', 'run_action', {click_action: '${segment.click_action}', ${table.pk_name}: ${JSON.stringify(row[table.pk_name])}})`;
1110
- }
1111
- },
1112
- };
1113
- };
1114
- const standardBlockDispatch = (viewname, state, table, extra, row) => {
1115
- const req = extra.req;
1116
- const fields = table.fields;
1117
- const locale = req.getLocale();
1118
- const role = req.user?.role_id || 100;
1119
- return {
1120
- field({ field_name, fieldview, configuration, click_to_edit }) {
1121
- let field = fields.find((fld) => fld.name === field_name);
1122
- if (!field)
1123
- return "";
1124
- let val = row[field_name];
1125
- let fvrun;
1126
- if (field &&
1127
- field.attributes &&
1128
- field.attributes.localized_by &&
1129
- field.attributes.localized_by[locale]) {
1130
- const localized_fld = field.attributes.localized_by[locale];
1131
- val = row[localized_fld];
1132
- }
1133
- const cfg = {
1134
- row,
1135
- ...field.attributes,
1136
- ...configuration,
1137
- };
1138
- if (fieldview && field.type === "File") {
1139
- if (req.generate_email)
1140
- cfg.targetPrefix = getSafeBaseUrl();
1141
- fvrun = val
1142
- ? getState().fileviews[fieldview].run(val, row[`${field_name}__filename`], cfg)
1143
- : "";
1144
- }
1145
- else if (fieldview &&
1146
- field.type &&
1147
- field.type.fieldviews &&
1148
- field.type.fieldviews[fieldview])
1149
- fvrun = field.type.fieldviews[fieldview].run(val, req, cfg);
1150
- else
1151
- fvrun = text(val);
1152
- if (click_to_edit &&
1153
- (role <= table.min_role_write || table.is_owner(req.user, row)))
1154
- return div({
1155
- "data-inline-edit-fielddata": encodeURIComponent(JSON.stringify({
1156
- field_name,
1157
- table_name: table.name,
1158
- pk: row[table.pk_name],
1159
- fieldview,
1160
- configuration,
1161
- })),
1162
- "data-inline-edit-ajax": "true",
1163
- "data-inline-edit-dest-url": `/api/${table.name}/${row[table.pk_name]}`,
1164
- class: !isWeb(req) ? "mobile-data-inline-edit" : "",
1165
- }, fvrun);
1166
- else
1167
- return fvrun;
1168
- },
1169
- join_field(jf) {
1170
- const { join_field, field_type, fieldview, configuration, target_field_attributes, click_to_edit, } = jf;
1171
- const keypath = join_field.split(".");
1172
- let value;
1173
- if (join_field.includes("->")) {
1174
- const [relation, target] = join_field.split("->");
1175
- const [ontable, ref] = relation.split(".");
1176
- const key = jf.targetNm ||
1177
- `${ref}_${ontable.replaceAll(" ", "").toLowerCase()}_${target}`;
1178
- value = row[validSqlId(key)];
1179
- }
1180
- else {
1181
- value = row[join_field.split(".").join("_")];
1182
- }
1183
- if (field_type === "File") {
1184
- return value
1185
- ? getState().fileviews[fieldview].run(value, "", configuration || {})
1186
- : "";
1187
- }
1188
- let fvRes;
1189
- if (field_type && fieldview) {
1190
- const type = getState().types[field_type];
1191
- if (type && getState().types[field_type]) {
1192
- fvRes = type.fieldviews[fieldview].run(value, req, {
1193
- row,
1194
- ...(target_field_attributes || {}),
1195
- ...configuration,
1196
- });
1197
- }
1198
- else
1199
- fvRes = text(value);
1200
- }
1201
- else
1202
- fvRes = text(value);
1203
- if (click_to_edit &&
1204
- (role <= table.min_role_write || table.is_owner(req.user, row)))
1205
- return div({
1206
- "data-inline-edit-fielddata": encodeURIComponent(JSON.stringify({
1207
- field_name: keypath[0],
1208
- table_name: table.name,
1209
- pk: row[table.pk_name],
1210
- fieldview,
1211
- configuration,
1212
- join_field: keypath[keypath.length - 1],
1213
- })),
1214
- "data-inline-edit-ajax": "true",
1215
- "data-inline-edit-dest-url": `/api/${table.name}/${row[table.pk_name]}`,
1216
- class: !isWeb(req) ? "mobile-data-inline-edit" : "",
1217
- }, fvRes);
1218
- else
1219
- return fvRes;
1220
- },
1221
- aggregation(column) {
1222
- const { agg_relation, stat, aggwhere, agg_field } = column;
1223
- let table, fld, through;
1224
- if (agg_relation.includes("->")) {
1225
- let restpath;
1226
- [through, restpath] = agg_relation.split("->");
1227
- [table, fld] = restpath.split(".");
1228
- }
1229
- else {
1230
- [table, fld] = agg_relation.split(".");
1231
- }
1232
- let targetNm = column.targetNm ||
1233
- db.sqlsanitize((stat +
1234
- "_" +
1235
- table +
1236
- "_" +
1237
- fld +
1238
- "_" +
1239
- (agg_field || "").split("@")[0] +
1240
- "_" +
1241
- aggwhere || "").toLowerCase());
1242
- if (targetNm.length > 58) {
1243
- targetNm = targetNm
1244
- .split("")
1245
- .filter((c, i) => i % 2 == 0)
1246
- .join("");
1247
- }
1248
- const val = row[targetNm];
1249
- if (stat.toLowerCase() === "array_agg" && Array.isArray(val))
1250
- return val.map((v) => text(v?.toString?.())).join(", ");
1251
- else if (column.agg_fieldview) {
1252
- const aggField = Table.findOne(table)?.getField?.(column.agg_field);
1253
- const outcomeType = stat === "Percent true" || stat === "Percent false"
1254
- ? "Float"
1255
- : stat === "Count" || stat === "CountUnique"
1256
- ? "Integer"
1257
- : aggField.type?.name;
1258
- const type = getState().types[outcomeType];
1259
- if (type?.fieldviews[column.agg_fieldview]) {
1260
- const readval = type.read(val);
1261
- return type.fieldviews[column.agg_fieldview].run(readval, req, column?.configuration || {});
1262
- }
1263
- }
1264
- return text(val);
1265
- },
1266
- action(segment) {
1267
- if (segment.action_style === "on_page_load") {
1268
- if (extra?.isPreview)
1269
- return "";
1270
- run_action_column({
1271
- col: { ...segment },
1272
- referrer: req?.get?.("Referrer"),
1273
- req: req,
1274
- }).catch((e) => Crash.create(e, req));
1275
- return "";
1276
- }
1277
- let url = action_url(viewname, table, segment.action_name, row, segment.rndid, "rndid", segment.confirm, undefined, !!segment.run_async);
1278
- if (segment.action_name === "Delete" &&
1279
- segment.configuration?.after_delete_action == "Reload page") {
1280
- url = {
1281
- javascript: `ajax_post('${table.delete_url(row)}', {success:()=>{close_saltcorn_modal();location.reload();}})`,
1282
- };
1283
- return action_link(url, req, segment);
1284
- }
1285
- else if (segment.action_name === "Delete")
1286
- url = `${table.delete_url(row, `redirect=${encodeURIComponent(interpolate(segment.configuration?.after_delete_url || "/", row, req?.user, "delete action: after delete URL"))}`)}`;
1287
- return action_link(url, req, segment);
1288
- },
1289
- view_link(view) {
1290
- const prefix = req.generate_email && req.get_base_url ? req.get_base_url() : "";
1291
- const { key } = view_linker(view, fields, (s) => s, isWeb(req), req.user, prefix, state, req, viewname);
1292
- return key(row);
1293
- },
1294
- tabs(segment, go) {
1295
- if (segment.tabsStyle !== "Value switch")
1296
- return false;
1297
- const rval = row[segment.field];
1298
- const value = rval?.id || rval; // TODO pkname of join table
1299
- const ix = segment.titles.findIndex((t) => typeof t.value === "undefined"
1300
- ? `${t}` === `${value}`
1301
- : value === t.value);
1302
- if (ix === -1)
1303
- return "";
1304
- return go(segment.contents[ix]);
1305
- },
1306
- blank(segment) {
1307
- if (segment.isHTML) {
1308
- return interpolate(segment.contents, { locale, ...row }, req?.user, "HTML element");
1309
- }
1310
- else
1311
- return segment.contents;
1312
- },
1313
- };
1314
- };
1315
- /**
1316
- * @param {object} column
1317
- * @param {object} f
1318
- * @param {object} req
1319
- * @param {*} __
1320
- * @returns {string}
1321
- */
1322
- const headerLabelForName = (label, fname, req, __, statehash) => {
1323
- //const { _sortby, _sortdesc } = req.query || {};
1324
- const _sortby = req?.query ? req.query[`_${statehash}_sortby`] : undefined;
1325
- const _sortdesc = req?.query
1326
- ? req.query[`_${statehash}_sortdesc`]
1327
- : undefined;
1328
- let arrow = _sortby !== fname
1329
- ? ""
1330
- : _sortdesc
1331
- ? i({ class: "fas fa-caret-down sortdir" })
1332
- : i({ class: "fas fa-caret-up sortdir" });
1333
- return arrow ? span({ class: "text-nowrap" }, label + arrow) : label;
1334
- };
1335
- /**
1336
- * @function
1337
- * @param {Field[]} fields
1338
- * @param {object} state
1339
- * @param {boolean} [fuzzyStrings]
1340
- * @returns {object}
1341
- */
1342
- const splitUniques = (fields, state, fuzzyStrings) => {
1343
- let uniques = {};
1344
- let nonUniques = {};
1345
- Object.entries(state).forEach(([k, v]) => {
1346
- const field = fields.find((f) => f.name === k);
1347
- if (field &&
1348
- (field.is_unique || field.primary_key) &&
1349
- fuzzyStrings &&
1350
- field.type &&
1351
- field.type.name === "String")
1352
- uniques[k] = { ilike: v };
1353
- else if (field && (field.is_unique || field.primary_key))
1354
- uniques[k] = field.type.read ? field.type.read(v, field.attributes) : v;
1355
- else
1356
- nonUniques[k] = v;
1357
- });
1358
- return { uniques, nonUniques };
1359
- };
1360
- /**
1361
- * @param {object} table
1362
- * @param {string} viewname
1363
- * @param {object[]} [columns]
1364
- * @param {object} layout0
1365
- * @param {boolean|null} id
1366
- * @param {object} req
1367
- * @param {boolean} isRemote
1368
- * @returns {Promise<Form>}
1369
- */
1370
- const getForm = async (table, viewname, columns, layout0, id, req, isRemote) => {
1371
- const fields = table.getFields();
1372
- const state = getState();
1373
- const tfields = (columns || [])
1374
- .map((column) => {
1375
- if (column.type === "Field") {
1376
- const f0 = fields.find((fld) => fld.name === column.field_name);
1377
- if (f0) {
1378
- const f = new Field(f0);
1379
- f.fieldview = column.fieldview;
1380
- if (f.type === "Key") {
1381
- if (state.keyFieldviews[column.fieldview])
1382
- f.fieldviewObj = state.keyFieldviews[column.fieldview];
1383
- f.input_type =
1384
- !f.fieldview ||
1385
- !f.fieldviewObj ||
1386
- (f.fieldview === "select" && !f.fieldviewObj)
1387
- ? "select"
1388
- : "fromtype";
1389
- }
1390
- if (f.type === "File") {
1391
- const fvNm = column.fieldview || "upload";
1392
- if (state.fileviews[fvNm])
1393
- f.fieldviewObj = state.fileviews[fvNm];
1394
- f.input_type =
1395
- !f.fieldview || !f.fieldviewObj ? "file" : "fromtype";
1396
- }
1397
- if (f.calculated) {
1398
- const qs = objToQueryString(column.configuration);
1399
- f.sourceURL = `/field/show-calculated/${table.name}/${f.name}/${f.fieldview}?${qs}`;
1400
- }
1401
- f.attributes = { ...column.configuration, ...f.attributes };
1402
- if (typeof column.block !== "undefined" &&
1403
- typeof f.attributes.block === "undefined")
1404
- f.attributes.block = column.block;
1405
- return f;
1406
- }
1407
- else if (table.name === "users" && column.field_name === "password") {
1408
- return new Field({
1409
- name: "password",
1410
- fieldview: column.fieldview,
1411
- type: "String",
1412
- });
1413
- }
1414
- else if (table.name === "users" &&
1415
- column.field_name === "passwordRepeat") {
1416
- return new Field({
1417
- name: "passwordRepeat",
1418
- fieldview: column.fieldview,
1419
- type: "String",
1420
- });
1421
- }
1422
- else if (table.name === "users" && column.field_name === "remember") {
1423
- return new Field({
1424
- name: "remember",
1425
- fieldview: column.fieldview,
1426
- type: "Bool",
1427
- });
1428
- }
1429
- }
1430
- })
1431
- .filter((tf) => !!tf);
1432
- const path = isWeb(req) ? req.baseUrl + req.path : "";
1433
- const qs = objectToQueryString(req.query);
1434
- let action = `/view/${viewname}${qs ? "?" + qs : ""}`;
1435
- if (path && path.startsWith("/auth/"))
1436
- action = path;
1437
- const layout = structuredClone(layout0);
1438
- traverseSync(layout, {
1439
- container(segment) {
1440
- if (segment.showIfFormula) {
1441
- segment.showIfFormulaInputs = segment.showIfFormula;
1442
- const fvs = [...freeVariables(segment.showIfFormula)];
1443
- const jfFvs = fvs.filter((fv) => fv.includes(".") && !fv.startsWith("user."));
1444
- if (jfFvs.length)
1445
- segment.showIfFormulaJoinFields = jfFvs
1446
- .map((jf) => {
1447
- const [ref, target] = jf.split(".");
1448
- const refField = table.getField(ref);
1449
- if (!refField || !refField?.reftable_name)
1450
- return null;
1451
- return {
1452
- ref: ref.replace("?", ""),
1453
- target,
1454
- refTable: refField.reftable_name,
1455
- refTablePK: Table.findOne(refField.reftable_name).pk_name,
1456
- };
1457
- })
1458
- .filter(Boolean);
1459
- }
1460
- },
1461
- });
1462
- if (!req.layout_hints)
1463
- req.layout_hints = state.getLayout(req.user).hints || {};
1464
- let isMobileLogin = false;
1465
- if (isRemote) {
1466
- const loginForm = getState().getConfig("login_form", "");
1467
- if (loginForm && viewname === loginForm)
1468
- isMobileLogin = true;
1469
- }
1470
- let submitActionJS = undefined;
1471
- const submitActionCol = columns.find((c) => c.is_submit_action);
1472
- if (submitActionCol) {
1473
- submitActionJS = `event.preventDefault();view_post(this, 'run_action', {rndid:'${submitActionCol.rndid}', ...get_form_record(this) })`;
1474
- if (layout.above)
1475
- layout.above.push(`<input type="submit" hidden />`);
1476
- //TODO what if there is no above, e.g. all in card or container
1477
- }
1478
- const form = new Form({
1479
- action: action,
1480
- onSubmit: isRemote || isOfflineMode()
1481
- ? `javascript:${!isMobileLogin
1482
- ? `formSubmit(this, '/view/', '${viewname}')`
1483
- : "loginFormSubmit(this)"}`
1484
- : submitActionJS,
1485
- viewname: viewname,
1486
- fields: tfields,
1487
- layout,
1488
- req,
1489
- pk_name: table.pk_name,
1490
- });
1491
- if (id)
1492
- form.hidden(form.pk_name);
1493
- return form;
1494
- };
1495
- const transformForm = async ({ form, table, req, row, res, getRowQuery, viewname, optionsQuery, state, }) => {
1496
- let originalState = state;
1497
- let pseudo_row = {};
1498
- if (!row) {
1499
- table.fields.forEach((f) => {
1500
- pseudo_row[f.name] = undefined;
1501
- });
1502
- }
1503
- const appState = getState();
1504
- const __ = db.is_node && appState
1505
- ? (s) => appState.i18n.__({ phrase: s, locale: req.getLocale() }) || s
1506
- : (s) => {
1507
- return s;
1508
- };
1509
- await traverse(form.layout, {
1510
- container(segment) {
1511
- if (segment.click_action) {
1512
- segment.url = `javascript:view_post(this, 'run_action', {click_action: '${segment.click_action}', ...get_form_record(this) })`;
1513
- }
1514
- },
1515
- async action(segment) {
1516
- if (segment.action_style === "on_page_load") {
1517
- segment.type = "blank";
1518
- segment.style = {};
1519
- if (segment.minRole && segment.minRole != 100) {
1520
- const minRole = +segment.minRole;
1521
- const userRole = req?.user?.role_id || 100;
1522
- if (minRole < userRole)
1523
- return;
1524
- }
1525
- if (req.method === "POST")
1526
- return;
1527
- //run action
1528
- try {
1529
- const actionResult = await run_action_column({
1530
- col: { ...segment },
1531
- referrer: req?.get?.("Referrer"),
1532
- req,
1533
- res,
1534
- table,
1535
- row: row || pseudo_row,
1536
- });
1537
- if (actionResult)
1538
- segment.contents = script(domReady(`common_done(${JSON.stringify(actionResult)}, "${viewname}")`));
1539
- }
1540
- catch (e) {
1541
- appState.log(5, `Error in Edit ${viewname} on page load action: ${e.message}`);
1542
- e.message = `Error in evaluating Run on Page Load action in view ${viewname}: ${e.message}`;
1543
- throw e;
1544
- }
1545
- }
1546
- if (segment.action_name === "Delete") {
1547
- if (form.values && form.values[table.pk_name]) {
1548
- segment.action_url = table.delete_url(form.values);
1549
- }
1550
- else {
1551
- segment.type = "blank";
1552
- segment.contents = "";
1553
- }
1554
- }
1555
- else if (segment.action_name === "form_action" &&
1556
- segment.configuration?.form_action === "Save" &&
1557
- table.fields.some((f) => f.type === "File")) {
1558
- let url = action_url(viewname, table, segment.action_name, row, segment.rndid, "rndid", segment.confirm);
1559
- if (url.javascript) {
1560
- //redo to include dynamic row
1561
- const confirmStr = segment.confirm
1562
- ? `if(confirm('Are you sure?'))`
1563
- : "";
1564
- url.javascript = `${confirmStr}view_post(this, 'run_action', get_form_data(this, '${segment.rndid}') );`;
1565
- }
1566
- segment.action_link = action_link(url, req, segment, __);
1567
- }
1568
- else if (!["Sign up", ...edit_build_in_actions].includes(segment.action_name) &&
1569
- !segment.action_name.startsWith("Login")) {
1570
- let url = action_url(viewname, table, segment.action_name, row, segment.rndid, "rndid", segment.confirm, undefined, segment.run_async);
1571
- if (url.javascript) {
1572
- //redo to include dynamic row
1573
- const confirmStr = segment.confirm
1574
- ? `if(confirm('Are you sure?'))`
1575
- : "";
1576
- // If this is a Multi-step action or the form/table contains File fields,
1577
- // post multipart FormData so req.files is populated server-side.
1578
- const hasFileFields = table.fields?.some((f) => f.type === "File");
1579
- if (segment.action_name === "Multi-step action" || hasFileFields) {
1580
- url.javascript = `${confirmStr}view_post(this, 'run_action', get_form_data(this, '${segment.rndid}') );`;
1581
- }
1582
- else {
1583
- url.javascript = `${confirmStr}view_post(this, 'run_action', {rndid:'${segment.rndid}', ...get_form_record(this)});`;
1584
- }
1585
- }
1586
- segment.action_link = action_link(url, req, segment, __);
1587
- }
1588
- },
1589
- join_field(segment) {
1590
- const qs = objToQueryString(segment.configuration);
1591
- segment.sourceURL = `/field/show-calculated/${table.name}/${segment.join_field}/${segment.fieldview}?${qs}`;
1592
- },
1593
- tabs(segment) {
1594
- const to_delete = new Set();
1595
- (segment.showif || []).forEach((sif, ix) => {
1596
- if (sif) {
1597
- const showit = eval_expression(sif, row || pseudo_row, req.user, "Tab show if formula");
1598
- if (!showit)
1599
- to_delete.add(ix);
1600
- }
1601
- });
1602
- segment.titles = segment.titles.filter((v, ix) => !to_delete.has(ix));
1603
- segment.contents = segment.contents.filter((v, ix) => !to_delete.has(ix));
1604
- (segment.titles || []).forEach((t, ix) => {
1605
- if (typeof t === "string" && t.includes("{{")) {
1606
- segment.titles[ix] = interpolate(t, row, req.user, "Tab titles");
1607
- }
1608
- });
1609
- },
1610
- view_link(segment) {
1611
- segment.type = "blank";
1612
- const view_select = parse_view_select(segment.view);
1613
- if (!row && view_select.type !== "Independent") {
1614
- segment.contents = "";
1615
- }
1616
- else {
1617
- const prefix = req.generate_email && req.get_base_url ? req.get_base_url() : "";
1618
- const { key } = view_linker(segment, table.fields, (s) => s, isWeb(req), req.user, prefix, req.query, req, viewname);
1619
- segment.contents = key(row || {});
1620
- }
1621
- },
1622
- async view(segment) {
1623
- //console.log(segment);
1624
- const view_select = parse_view_select(segment.view, segment.relation);
1625
- //console.log({ view_select });
1626
- const view = View.findOne({ name: view_select.viewname });
1627
- if (!view)
1628
- throw new InvalidConfiguration(`Cannot find embedded view: ${view_select.viewname}`);
1629
- // check if the relation path matches a ChildList relations
1630
- let childListRelPath = false;
1631
- if (segment.relation && view.table_id) {
1632
- const targetTbl = Table.findOne({ id: view.table_id });
1633
- const relation = new Relation(segment.relation, targetTbl.name, displayType(await view.get_state_fields()));
1634
- childListRelPath = relation.type === RelationType.CHILD_LIST;
1635
- }
1636
- // Edit-in-edit
1637
- if (view.viewtemplate === "Edit" &&
1638
- (view_select.type === "ChildList" || childListRelPath)) {
1639
- if (childListRelPath)
1640
- updateViewSelect(view_select);
1641
- const childTable = Table.findOne({ id: view.table_id });
1642
- const childForm = await getForm(childTable, view.name, view.configuration.columns, view.configuration.layout, row?.id, req, !isWeb(req));
1643
- traverseSync(childForm.layout, {
1644
- field(segment) {
1645
- segment.field_name = `${view_select.field_name}.${segment.field_name}`;
1646
- },
1647
- });
1648
- for (const field of childForm.fields) {
1649
- if (field.name === childTable.pk_name) {
1650
- field.class = field.class
1651
- ? `${field.class} omit-repeater-clone`
1652
- : "omit-repeater-clone";
1653
- }
1654
- }
1655
- await childForm.fill_fkey_options(false, optionsQuery, req.user);
1656
- const fr = new FieldRepeat({
1657
- name: view_select.field_name,
1658
- label: view_select.field_name,
1659
- fields: childForm.fields,
1660
- layout: childForm.layout,
1661
- metadata: {
1662
- table_id: childTable.id,
1663
- view: segment.view,
1664
- relation: view_select.field_name,
1665
- relation_path: segment.relation,
1666
- order_field: segment.order_field,
1667
- },
1668
- });
1669
- if (row?.id) {
1670
- const childRows = getRowQuery
1671
- ? await getRowQuery(view.table_id, view_select, row.id, segment.order_field)
1672
- : await childTable.getRows({
1673
- [view_select.field_name]: row.id,
1674
- }, segment.order_field ? { orderBy: segment.order_field } : {});
1675
- fr.metadata.rows = childRows;
1676
- if (!fr.fields.map((f) => f.name).includes(childTable.pk_name))
1677
- fr.fields.push({
1678
- name: childTable.pk_name,
1679
- input_type: "hidden",
1680
- });
1681
- }
1682
- form.fields.push(fr);
1683
- segment.type = "field_repeat";
1684
- segment.field_repeat = fr;
1685
- return;
1686
- } // end edit in edit
1687
- const outerState = {};
1688
- Object.entries(originalState || {}).forEach(([k, v]) => {
1689
- if (k.startsWith("_"))
1690
- outerState[k] = v;
1691
- });
1692
- let state = {};
1693
- let urlFormula;
1694
- let needFields = new Set();
1695
- if (view_select.type === "RelationPath" && view.table_id) {
1696
- const pathToUrlFormula = (relation) => {
1697
- const st = pathToState(relation, (k) => `row.` + k);
1698
- return Object.entries(st)
1699
- .map(([k, v]) => {
1700
- needFields.add(v.split(".")[1]);
1701
- return `${k}='+${v}+'`;
1702
- })
1703
- .join("&");
1704
- };
1705
- const targetTbl = Table.findOne({ id: view.table_id });
1706
- if (targetTbl) {
1707
- const relation = new Relation(segment.relation, targetTbl.name, displayType(await view.get_state_fields()));
1708
- const relFmlQS = pathToUrlFormula(relation);
1709
- const type = relation.type;
1710
- if (!row && type == RelationType.OWN) {
1711
- segment.type = "blank";
1712
- urlFormula = `add_extra_state('/view/${view.name}/?${relFmlQS}', ${JSON.stringify(segment.extra_state_fml)}, row, ${JSON.stringify(outerState)})`;
1713
- segment.contents = segment.contents = div({
1714
- class: "d-inline",
1715
- "data-sc-embed-viewname": view.name,
1716
- "data-view-source-need-fields": [...needFields].join(","),
1717
- "data-view-source": encodeURIComponent(urlFormula),
1718
- });
1719
- return;
1720
- }
1721
- else if (!row &&
1722
- type !== RelationType.INDEPENDENT &&
1723
- !relation.isFixedRelation()) {
1724
- urlFormula = `add_extra_state('/view/${view.name}/?${relFmlQS}', ${JSON.stringify(segment.extra_state_fml)}, row, ${JSON.stringify(outerState)})`;
1725
- segment.contents = segment.contents = div({
1726
- class: "d-inline",
1727
- "data-sc-embed-viewname": view.name,
1728
- "data-view-source-need-fields": [...needFields].join(","),
1729
- "data-view-source": encodeURIComponent(urlFormula),
1730
- });
1731
- return;
1732
- }
1733
- const userId = req?.user?.id;
1734
- state = pathToState(relation, relation.isFixedRelation() ? () => userId : (k) => row[k]);
1735
- urlFormula = `add_extra_state('/view/${view.name}?${relFmlQS}', ${JSON.stringify(segment.extra_state_fml)}, row, ${JSON.stringify(outerState)})`;
1736
- }
1737
- }
1738
- else {
1739
- const isIndependent = view_select.type === "Independent";
1740
- // legacy none check ?
1741
- if (!view)
1742
- throw new InvalidConfiguration(`Edit view incorrectly configured: cannot find embedded view ${view_select.viewname}`);
1743
- switch (view_select.type) {
1744
- case "Own":
1745
- state = { id: row?.id };
1746
- urlFormula = `add_extra_state('/view/${view.name}/?id='+row.id, ${JSON.stringify(segment.extra_state_fml)}, row, ${JSON.stringify(outerState)})`;
1747
- needFields.add("id");
1748
- break;
1749
- case "Independent":
1750
- state = {};
1751
- urlFormula = `add_extra_state('/view/${view.name}/?id='+row.id, ${JSON.stringify(segment.extra_state_fml)}, row, ${JSON.stringify(outerState)})`;
1752
- needFields.add("id");
1753
- break;
1754
- case "ChildList":
1755
- case "OneToOneShow":
1756
- state = { [view_select.field_name]: row?.id };
1757
- urlFormula = `add_extra_state('/view/${view.name}/?${view_select.field_name}='+row.id, ${JSON.stringify(segment.extra_state_fml)}, row, ${JSON.stringify(outerState)})`;
1758
- needFields.add("id");
1759
- break;
1760
- case "ParentShow":
1761
- state = { id: row?.[view_select.field_name] };
1762
- urlFormula = `add_extra_state('/view/${view.name}/?id='+row.${view_select.field_name}, ${JSON.stringify(segment.extra_state_fml)}, row, ${JSON.stringify(outerState)})`;
1763
- needFields.add(view_select.field_name);
1764
- break;
1765
- }
1766
- if (!row && !isIndependent) {
1767
- segment.type = "blank";
1768
- segment.contents = div({
1769
- class: "d-inline",
1770
- "data-sc-embed-viewname": view.name,
1771
- "data-view-source-need-fields": [...needFields].join(","),
1772
- "data-view-source": encodeURIComponent(urlFormula),
1773
- });
1774
- return;
1775
- }
1776
- }
1777
- const extra_state = segment.extra_state_fml
1778
- ? eval_expression(segment.extra_state_fml, {
1779
- ...dollarizeObject(req.query),
1780
- session_id: getSessionId(req),
1781
- ...(row || pseudo_row),
1782
- }, req.user, `Extra state formula for embedding view ${view.name}`)
1783
- : {};
1784
- const qs = stateToQueryString({ ...state, ...outerState, ...extra_state }, true);
1785
- segment.contents = div({
1786
- class: "d-inline",
1787
- "data-sc-embed-viewname": view.name,
1788
- "data-sc-view-source": `/view/${view.name}${qs}`,
1789
- "data-view-source-current": `/view/${view.name}${qs}`,
1790
- "data-view-source-need-fields": [...needFields].join(","),
1791
- "data-view-source": encodeURIComponent(urlFormula),
1792
- }, view.renderLocally()
1793
- ? await view.run({ ...state, ...outerState, ...extra_state }, { req, res }, view.isRemoteTable())
1794
- : await renderServerSide(view.name, {
1795
- ...state,
1796
- ...outerState,
1797
- ...extra_state,
1798
- }));
1799
- },
1800
- });
1801
- translateLayout(form.layout, req.getLocale());
1802
- if (req.headers?.saltcornmodalrequest)
1803
- form.xhrSubmit = true;
1804
- setDateLocales(form, req.getLocale());
1805
- };
1806
- const setDateLocales = (form, locale) => {
1807
- form.fields.forEach((f) => {
1808
- if (f.type && f.type.name === "Date") {
1809
- f.attributes.locale = locale;
1810
- }
1811
- });
1812
- };
1813
- /**
1814
- * update viewSelect so that it looks like a normal ChildList
1815
- */
1816
- const updateViewSelect = (viewSelect) => {
1817
- if (viewSelect.path.length === 1) {
1818
- viewSelect.field_name = viewSelect.path[0].inboundKey;
1819
- viewSelect.table_name = viewSelect.path[0].table;
1820
- }
1821
- else if (viewSelect.path.length === 2) {
1822
- viewSelect.field_name = viewSelect.path[1].inboundKey;
1823
- viewSelect.table_name = viewSelect.path[1].table;
1824
- viewSelect.throughTable = viewSelect.path[0].inboundKey;
1825
- viewSelect.through = viewSelect.path[0].table;
1826
- }
1827
- };
1828
- /**
1829
- * @param {object} table
1830
- * @param {object} req
1831
- * @param {object} fixed
1832
- * @returns {Promise<object>}
1833
- */
1834
- const fill_presets = async (table, req, fixed) => {
1835
- if (!table)
1836
- return fixed;
1837
- const fields = table.getFields();
1838
- Object.keys(fixed || {}).forEach((k) => {
1839
- if (k.startsWith("preset_")) {
1840
- if (fixed[k]) {
1841
- const fldnm = k.replace("preset_", "");
1842
- const fld = fields.find((f) => f.name === fldnm);
1843
- if (fld) {
1844
- if (table.name === "users" && fld.primary_key)
1845
- fixed[fldnm] = req.user ? req.user.id : null;
1846
- else
1847
- fixed[fldnm] = fld.presets[fixed[k]]({
1848
- user: req.user,
1849
- req,
1850
- field: fld,
1851
- });
1852
- }
1853
- }
1854
- delete fixed[k];
1855
- }
1856
- else {
1857
- const fld = fields.find((f) => f.name === k);
1858
- if (!fld)
1859
- delete fixed[k];
1860
- if (fixed[k] === null || fixed[k] === "")
1861
- delete fixed[k];
1862
- }
1863
- });
1864
- return fixed;
1865
- };
1866
- const objToQueryString = (o) => Object.entries(o || {})
1867
- .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
1868
- .join("&");
1869
- // edit template build in actions
1870
- const edit_build_in_actions = [
1871
- "Save",
1872
- "SaveAndContinue",
1873
- "UpdateMatchingRows",
1874
- "SubmitWithAjax",
1875
- "Reset",
1876
- "GoBack",
1877
- "Delete",
1878
- "Cancel",
1879
- ];
1880
- module.exports = {
1881
- get_viewable_fields,
1882
- get_viewable_fields_from_layout,
1883
- action_url,
1884
- objToQueryString,
1885
- action_link,
1886
- view_linker,
1887
- parse_view_select,
1888
- splitUniques,
1889
- getForm,
1890
- fill_presets,
1891
- get_view_link_query,
1892
- make_link,
1893
- edit_build_in_actions,
1894
- standardBlockDispatch,
1895
- standardLayoutRowVisitor,
1896
- setDateLocales,
1897
- transformForm,
1898
- updateViewSelect,
1899
- };
2
+ //for backwards compatibility - some modules import from here
3
+ module.exports = require("../../viewable_fields");
1900
4
  //# sourceMappingURL=viewable_fields.js.map