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